One Lambda, many languages

Ryan Cormack
4 min readSep 17, 2021

--

Often, as developers, we find ourselves in our comfort zone sticking to a formula that we know works. For me and my team, this tends to be .NET running on Lambda. But when we need to expose a public synchronous API, the cold start times on Lambda create a subpar user experience.

You may want to do this when you need a specific library or language capability in one function, or when you have several functions with certain non-functional requirements (such as a low latency service level objective). Recently I’ve had to expose some data to public clients where the 3+ second cold starts would meet our product requirements.

We can use the Serverless Framework to deploy a Lambda with different runtimes across the different functions. Let's take a look at how.

Serverless for the provider object

In the Serverless Framework, the values in the Provider object can often be over-written at the function level. We’re going to make use of that here to assign different timeouts, memory, and runtimes to each of our functions, so we’ve left those values out of our Provider.
Lines 9 and 10 are important. By telling the framework we want to individually package our functions, when AWS provisions the Lambda Functions, we have different code archives that the service has to bootstrap at startup. This is important for reducing package size for cold starts as well as making sure we’re not packing up anything we don’t need to run the specific function.

Function definitions

Then we’ve got our function definitions. You can see we have a Worker defined on line 13 and an API defined on line 23. On lines 14 and 24 we are setting specific runtimes for each function. I’m using .NET Core for my complex domain logic in the worker and Node for my lightweight Node API.
Next, we’ve got the specific setup for how we’re packaging the individual functions. For the Lambda Worker, I’m specifying a zip archive created by AWS’s Lambda Tools. For the Node function, there is no need to manually zip the package. Because I’m using Typescript, I’m transpiling my Typescript file into a single Javascript file that the Serverless framework will zip up for me. I’m telling the Serverless Framework to ignore every other file in my repo, keeping it as light as possible.

Once we’ve built the .NET DLLs, transpired the Typescript, and built my Cloudformation stack using serverless package we get two neatly packaged up functions ready to be deployed.

We’ve got the .NET function configured with its higher memory and longer timeout to accommodate the cold start and perform its complex domain logic.

Then we’ve got the NodeJS function, with its much smaller memory footprint and far lower timeout — we don’t want the client's device just hanging and waiting. We should fail fast and retry often.

The implementation of the two functions doesn’t matter much here. In my code example the functions just log out some data, but in the real world, the differences are quite impressive. This is based on a similar approach I’ve taken at work to optimize the client experience for one of our services. We’re building a complex object in S3, based on an SNS trigger and exposing a Lambda Function that returns a signed URL. The code for signing the URL is only about 20 lines in Typescript and not much more in .NET but the .NET cold starts are in the region of 4 seconds and the NodeJS cold starts are under 50ms. When you have a service that is both important to write solid domain logic as well as provide rapid responses to a client, mixing your language and frameworks may well be the best approach to take. The Serverless Framework makes it really easy to build and package functions taking this approach.

In summary, we’ve looked at how we can use the Serverless Framework to package both a .NET function and a NodeJS function into the same Lambda, allowing engineers to use one language to process data and another language to serve that data to a client. Everything in software engineering has pros and cons. Languages are no different, so it’s important to make sure you’re using the right tool the for the job. All the code for this example can be found on my Github, here.

--

--

Ryan Cormack

Serverless engineer and AWS Community Builder working on event driven AWS solutions