Writing portable serverless applications

Danilo Poccia
Datree
Published in
4 min readApr 3, 2018

--

A question I sometimes get is how to build serverless applications that can easily be used in other environments, for example having the same business logic running on AWS Lambda and on Docker containers.

Writing business logic that can run on different environments.

It is actually simple to write code that can be reused in multiple ways. The most important takeaway is to separate the Lambda handler from your core logic. The Lambda handler is the function, within your code, that AWS Lambda is calling when the Lambda function is invoked.

Use the Lambda handler as a wrapper for your business logic, managing the input syntax for the event, and eventually the output back to the AWS service in case of synchronous invocations, such as for the Amazon API Gateway.

In a way, the idea is just to reuse the adapter pattern from the old days to have your business logic accessible via different interfaces.

For the sake of simplicity, I am going to use Node.js here for the practical examples, but something similar can, of course, be done with other Lambda runtimes, such as Java, Go or Python. This is also giving me a good excuse to play with the new Node.js 8.10 runtime now available in AWS Lambda.

For example, this is a simple “Hello World” Lambda function separating the business logic from the adapter interface:

The core logic is in the greetingsFor function, that expects in input a name and returns some greetings. For example, it can return “Hello” and your name, or “Hello World” if no name is provided.

The adapter, in this case, is an event wrapper for AWS Lambda and is in the handler function, where a name is taken from the event and passed to the greetingsFor function. The return of the function is then passed back to the Lambda platform.

What happens if I want to use a different event syntax? For example, let’s transform the above function into a web API that can be accessed via the Amazon API Gateway, or run anywhere using the Node.js Express framework:

The business logic function greetingsFor is exactly the same as before, but now we have two different adapters:

  • The handler function is looking for the input name in the query parameters of the event received from the Amazon API Gateway, using the default Lambda Proxy integration.
  • If the code is not running on AWS Lambda, the Node.js Express framework is used to transform the Lambda function into a web application. Adding a Dockerfile to containerize the web application is trivial.

In this way, the same code can be used for a Lambda function and for a traditional Web application. To check if the code is running on AWS Lambda, you can, for example, look for a couple of default environment variables available in the Lambda Execution Environment.

To run the previous function locally, just npm install express before starting the application. Note that I am not importing the Node.js Express framework when the code is executed on AWS Lambda. This is a best practice to keep both your function code and memory footprint smaller.

If your application is using automatic tests, you can have them running on both AWS Lambda (in the cloud or using SAM Local) and a Docker container to continuously check that you are not creating, over time, dependencies on one of the different environments.

An even better approach is to split business logic and adapters (such as event wrappers and web interfaces) into different files. In the previous example you would have:

  • a greetings.js file, with your business logic only, exporting the greetingsFor function
  • an event_APIGateway.js file, that contains the handler function for AWS Lambda (following the API Gateway event syntax) and require greetings.js as a Node.js module
  • an app.js file, that provides a web interface using the Node.js Express framework, again importing greetings.js as a Node.js module
Using the Adapter Pattern to write portable serverless applications.

If you need more ways to use your business logic, you can add more adapters. For example, to use the same business logic triggered by other AWS services you can add event_Kinesis.js, event_SNS.js, and so on.

In the case of a Kinesis Data Stream, data is processed in micro-batches and a single event usually includes more than one record to process. The event_Kinesis.js adapter would apply your business logic to each of the records in input.

The next time you are going to create a new application, think of the different adapters you could need, and start by splitting the business logic from its interfaces. And don’t forget to share what you are going to build with me!

--

--

Danilo Poccia
Datree

Passioned about IT, IoT, AI, ML, and other acronyms. Writing the Chronicles of Computation book. Chief Evangelist (EMEA) @ AWS. Opinions are my own.