{"id":17355138,"url":"https://github.com/zephinzer/ts-serverless-20180427","last_synced_at":"2025-06-18T02:36:53.222Z","repository":{"id":79926701,"uuid":"130443772","full_name":"zephinzer/ts-serverless-20180427","owner":"zephinzer","description":"Technical sharing on serverless technology using the Fn Project","archived":false,"fork":false,"pushed_at":"2018-04-21T07:19:02.000Z","size":5,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T14:16:57.253Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zephinzer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-21T05:43:07.000Z","updated_at":"2018-04-21T07:19:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"44437232-0407-4d8d-a3ff-f936e4b85098","html_url":"https://github.com/zephinzer/ts-serverless-20180427","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zephinzer/ts-serverless-20180427","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fts-serverless-20180427","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fts-serverless-20180427/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fts-serverless-20180427/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fts-serverless-20180427/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zephinzer","download_url":"https://codeload.github.com/zephinzer/ts-serverless-20180427/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephinzer%2Fts-serverless-20180427/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260475878,"owners_count":23014931,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T17:42:27.215Z","updated_at":"2025-06-18T02:36:48.210Z","avatar_url":"https://github.com/zephinzer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Technical Sharing on Serverless Architecture\nThis repository contains visual aids and example scripts/manifests for a technical sharing done on serverless architectures, first presented on the 27th of April 2018 for the Agile Consulting \u0026 Engineering monthly technical sharing.\n\nWe utilise the [open-source fnproject](https://github.com/fnproject) for examples herein.\n\n## Pre-Reading TODO\n\n- Clone this repository locally.\n- Run all commands from a terminal/command prompt at this repository.\n- Commands are catered for *nix environment, if you can contribute the Windows equivalents, please do with a PR.\n- Any `curl`-ed scripts here pose a security threat to your system. While I have verified at point of publishing that none of them are malicious, time and open-source contributions may change that.\n- We shall refer to all shells, command prompts and terminals universally as terminal.\n\n## Getting Started\n### Download Fn\n\u003e **NOTE**: the Linux/OS X `curl` commands may take awhile to complete depending on your internet connection. At the end when it's done, you should see the following:\n\n```\nfn version 0.4.74\n\n        ______\n       / ____/___\n      / /_  / __ \\\n     / __/ / / / /\n    /_/   /_/ /_/`\n```\n\n#### Linux\nRun the following script in a terminal:\n\n```sh\ncurl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh\n```\n#### Mac OS X\nRun the following script in a terminal:\n\n```sh\nbrew install fn\n# OR\ncurl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh\n```\n#### Windows\nDownload the latest version from [https://github.com/fnproject/cli/releases](https://github.com/fnproject/cli/releases).\n\n- - -\n\n### Fire Up Fn\nOpen a dedicated terminal at the repository's directory and run the following command to start Fn (dedicated because that terminal will not longer be used for anything other than Fn logs output):\n\n```sh\nfn start\n```\n\nThis pulls the `fnproject/fnserver:latest` Docker image from the public Docker registry and runs it.\n\nVerify that you can access it by visiting http://localhost:8080 and yielding a `{\"goto\":\"https://github.com/fnproject/fn\",\"hello\":\"world!\"}` response:\n\n```sh\ncurl -sSL http://localhost:8080 | grep 'hello\":\"world!' \u0026\u0026 echo 'fn is accessible!';\n```\n\n- - -\n\n### Initialise a Project\nRun the following script to create an `example` directory to store your function:\n\n```sh\nfn init --runtime node example\n```\n\nObserve the newly created directory `example` and its contents:\n\n```md\n- example\n  - func.js       (-\u003e code)\n  - func.yaml     (-\u003e fn definitions)\n  - package.json  (-\u003e node package management)\n  - test.json     (-\u003e contract test schema)\n```\n\n- - -\n\n### Run the Project Locally\n\nNavigate into it (`cd example`) and test drive it:\n\n```sh\nfn run\n```\n\nThis initiates a Docker build process (explained more later).\n\nYou should see:\n\n```\nBuilding image example:0.0.1 ............................\n{\"message\":\"Hello World\"}\n```\n\n#### Quirk #1: Build-Time Errors\nLet's see what happens when we inject a build-time error. Modify `func.js` so that it looks like:\n\n```js\n// func.js\nvar fdk=require('@fnproject/fdk');\n\nfdk.handle(function(input){\n  var name = 'Worl\n  if (input.name) {\n    name = input.name;\n  }\n  response = {'message': 'Hello ' + name}\n  return response\n})\n```\n\nRun `fn run` again. Observe the error. Note that while running `node func.js` would have yielded the error immediately, Fn's build process still runs successfully despite language syntax errors.\n\n#### Quirk #2: Run-Time Errors\nNow let's see what happens when we inject a run-time error. Modify `func.js` so that it looks like:\n\n```js\n// func.js\nvar fdk=require('@fnproject/fdk');\n\nfdk.handle(function(input){\n  throw new Error('what now doc?')\n  return response\n})\n```\n\nDo an `fn run` and observe the behaviour. Nothing special about this one.\n\n#### Quirk #3: Logging Errors\nFinally, let us attempt to log a message for easier debugging:\n\n```js\n// func.js\nvar fdk=require('@fnproject/fdk');\n\nfdk.handle(function(input){\n  console.info('hi');\n  const response = {'message': 'Hello '}\n  return response\n})\n```\n\nDo an `fn run` now, and observe the message:\n\n```\nerror decoding invalid character 'h' looking for beginning of value\n```\n\nIn a nutshell, we can no longer use the various `console.*` we are accustomed to. The only method still available is `console.error`. Try change the line `console.info('hi');` to `console.error('hi');` and we'll get what we were looking for:\n\n```sh\n# output\nBuilding image example:0.0.1 .\nhi\n{\"message\":\"Hello \"}\n```\n\n#### TL;DR\n1. you can't rely on the build process, always run your function before dpeloying\n2. throw your errors, this way, it'll always be in the logs\n3. use `console.error` or `process.stderr.write` for debugging purposes\n\n- - -\n\n### Running the Project on Fn\n\nRevert `func.js` to the original state:\n\n```js\n// func.js\nvar fdk=require('@fnproject/fdk');\n\nfdk.handle(function(input){\n  var name = 'World';\n  if (input.name) {\n    name = input.name;\n  }\n  response = {'message': 'Hello ' + name}\n  return response\n})\n```\n\nNow let's deploy it to an example application, `example`, using:\n\n```sh\nfn deploy --app example --local;\n```\n\nObserve the output:\n\n```\nDeploying example to app: example at path: /example\nBumped to version 0.0.2\nBuilding image example:0.0.2 .\nUpdating route /example using image example:0.0.2...\n```\n\nLet's test it by curling it now:\n\n```sh\ntime curl http://localhost:8080/r/example/example\n```\n\nThe first time we run this, we notice a lag time. Your output probably looks like:\n\n```\n{\"message\":\"Hello World\"}curl http://localhost:8080/r/example/example  0.02s user 0.01s system 21% cpu 3.083 total\n```\n\nNote the last value (`3.083`) is in seconds. It took us 3 seconds to run the function.\n\nNow run the same command again and notice that that number drops to `0.*`. This is due to the way Fn manages your functions.\n\n- - -\n\n### Testing an Fn function\nThe Fn Node template comes with a `test.json` that provides an easy way to do contract testing as a unit test. Open the file in a text editor and observe that it defines an array of two test cases, each case containing an `input` and `output`.\n\nWhen running this in-built contract test mechanism, Fn sends the input to your application and checks to see if the output matches the input. If it does, it passed. If it doesn't, it failed.\n\nLet's try it out:\n\n```sh\nfn test\n```\n\nObserve the output:\n\n```\nBuilding image example:0.0.2 .\nRunning 2 tests on /path/to/ts-serverless-20180427/example/func.yaml (image: example:0.0.2):\nTest 1\nPASSED -    ( 2.846489226s )\n\nTest 2\nPASSED -    ( 2.623483408s )\n\ntests run: 2 passed, 0 failed\n```\n\n- - -\n\n### Building an Fn function\nShould we wish to just build the function, there's a command for that:\n\n```sh\nfn build\n```\n\nThis builds the function using information from `func.yaml` but does not run it. When building a build/release pipeline for deploying functions, remember that **syntax errors will not show up at build-time if we are using an interpreted language**.\n\nSo what happens at build-time? Let's challenge it with some unexpected values. Add a new dependency to `package.json` **manually** by opening the file and adding a new line under the `\"dependencies\"` property:\n\n```json\n{\n  ...,\n  \"dependencies\": {\n    \"@fnproject/fdk\": \"0.x\",\n    \"this-definitely-doesnt-exist\": \"nope\"\n  }\n}\n```\n\nSpoiler: this will error out, so run the build command as such to get logs:\n\n```sh\nfn --verbose build\n```\n\nNote that the build process is essentially a `docker build .` with special parameters. So Fn uses Docker containers under the hood!\n\n- - -\n\n## Assessing Performance\n### Setting up Fn variant\nCreate a new project using Fn:\n\n```sh\nfn init --runtime node withoutserver\n```\n\nChange the `func.js` so that it looks like:\n\n```js\n// withoutserver/func.js\nvar fdk=require('@fnproject/fdk');\n\nfdk.handle(function(input){\n  response = 'hi';\n  return response;\n});\n```\n\nDeploy it using:\n\n```sh\nfn deploy --app example --local\n```\n\nTest it is working:\n\n```sh\ncurl http://localhost:8080/r/example/withoutserver\n```\n\n### Setting up Vanilla variant\nCreate a new directory with a `server.js` file manually:\n\n```sh\nmkdir withserver;\ntouch server.js;\n```\n\nPaste the following code into `server.js`: \n\n```js\n// withserver/server.js\nconst http = require('http');\n\nconst server = http.createServer((req, res) =\u003e {\n  res.end('\"hi\"');\n});\nserver.listen(8081);\n```\n\nRun the server:\n\n```js\nnode ./server.js\n```\n\nOpen a new terminal and test that it works:\n\n```sh\ncurl http://localhost:8081\n```\n\nObserver that the output is exactly the same for the Fn and the Vanilla variants. Let's put them to the test.\n\n### Introducing Autocannon\nAutocannon is a handly tool for running targetted load tests against an API endpoint. Install it with:\n\n```sh\ncommand -v autocannon || npm i -g autocannon \u0026\u0026 echo 'autocannon is installed';\n```\n\nLet's use Autocannon to simulate a huge load coming into Fn and Vanilla Node:\n\n```sh\nautocannon --connections 10 --duration 30 --connectionRate 10 -T withoutserver \"http://localhost:8080/r/example/withoutserver\";\nsleep 1;\nautocannon --connections 10 --duration 30 --connectionRate 10 -T withserver \"http://localhost:8081\";\n```\n\nFor the variant using Fn, my machine reports:\n\n```\nRunning 30s test @ http://localhost:8080/r/example/example__withoutserver\n10 connections\n\nStat         Avg     Stdev   Max\nLatency (ms) 338.97  799.04  7248.65\nReq/Sec      24.37   21      58\nBytes/Sec    4.37 kB 3.78 kB 10.3 kB\n\n731 requests in 30s, 130 kB read\n5 errors (5 timeouts)\n```\n\nFor the variant using vanilla Node, it is:\n\n```\nRunning 30s test @ http://localhost:8081\n10 connections\n\nStat         Avg     Stdev   Max\nLatency (ms) 3.97    5.14    41.65\nReq/Sec      100     10.81   133\nBytes/Sec    10.3 kB 1.14 kB 13.7 kB\n\n3k requests in 30s, 309 kB read\n```\n\nNote that compared to vanilla node, a serverless architecture:\n\n- has 100x more latency than vanilla node\n- processes requests 5x slower than vanilla node\n- reads data about 2x as slow\n\nOn overall, given no processing and perfect conditions, a serverless architecture performs about 3x worst (3k requests for vanilla node vs 731 requests for serverless).\n\nSo why serverless?\n\n- - -\n\n## Serverless Usage\n\n\n`-- WIP --`\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephinzer%2Fts-serverless-20180427","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzephinzer%2Fts-serverless-20180427","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephinzer%2Fts-serverless-20180427/lists"}