There’s a new reversion to the mean: Choose Boring Technology followed up by Listen Notes’ healthy dose of pragmatism: Listen Notes – The boring technology behind a one-person Internet company.

I’ll share some notes about AWS Lambda and why it is about as boring as you can get. It doesn’t have any Ansible scripts to manage, nor any EC2 instances or even an Ubuntu OS to tinker with.

There are a number of deployment tools that make single-click deploys a breeze, a few are listed here:

AWS ALB vs API Gateway (APIG)

API Gateawy (APIG) is pretty good but for a lot of use-cases it is cost-prohibitive and uneccessary. If you need authentication and authorization via IAM or transforming requests and responses, then APIG might make sense. For other cases, the Application Load Balancer (ALB) will fit the bill. Transforming requests and responses is not that useful and you could just handle that in your code if you are expecting firstName and the request contains fName to support an older version. Similarly, authentication can also be done in your code, but if you rely on AWS Identity and Access Management (IAM) then APIG can take some pain out of that.

If you want to save some money and get some ease and flexibility, then go with ALB for handling HTTP requests to your serverless Lambda function.

AWS announced Lambda support for ALB in November 2018. The announcement also contains a basic walkthrough to set up ALB.

Lambda + Express.JS + ALB + ClaudiaJS = 👌

The excellent awslabs/aws-serverless-express package lets you wrap any Express.js server code into serverless Lambda. The beauty of this setup is there is no lock-in. You can run it on your local machine, on a cloud instance, or a serverless setup.

Let’s see a basic example app with this setup. This is your typical express.js server with a minor modification to app.listen().

/* src/express.js */
const app = require("express");
const isLocal = !!process.env.local;

app.get("/", (req, res) => res.send("Hello World");

/* AWS Lambda doesn't need a listener as it's bootstrapped via
   aws-serverless-express. We only need it for other environments. */
if (isLocal) {
    app.listen(3000, () => { console.log(`Listening on ${port}`));
}

module.exports = app;

Now you are ready to run this on your local dev box via local=true env so that it starts the server on http://localhost:3000:

local=true node ./src/express.js

Next up is the lambda.js bootstrap. This is exported as via exports.handlerand is the initial entry point for all Lambda executions. Since awsServerlessExpress is the handler, it processes the HTTP request – this is why we don’t need app.listen() in our express.js file. The request is then passed on to express.js which handles it as usual.

// src/lambda.js
const awsServerlessExpress = require("aws-serverless-express");
const app = require("./express");
const serverless = awsServerlessExpress.configure({ app });
exports.handler = serverless.handler;

For a more complete example, see https://github.com/awslabs/aws-serverless-express/tree/v4/examples/alb/src

Next up, you need to deploy this code. You can experiment by adding this via the Lambda Web Console or uploading it as a zip.

I prefer to use Claudia.JS for Lambda deployments as it’s very simple to get up and running:

claudia update --version production --handler src/lambda.handler

Next up, you’ll need to setup the ALB and “Target Groups” to point to the lambda function (enable the check on Multi-value Headers option in the Target Group). I won’t get into how to set this up, as there are plenty of tutorials and it takes less than 5 minutes. The AWS announcement for ALB + Lambda also has a walkthrough of the setup. However, the screenshots below highlight a sample setup.

ALB Setup

Target Group Setup

Enable Multi-Value Headers

ALB Routing is Your Friend

ALB can do path-based routing so example.com/screenshot goes lambda-screenshot and example.com/crawl to lambda-crawl, even though the domains are the same.

This path-based routing is very handy. If you feel like routing things back to your monolithic app on EC2, you can do that with a few clicks. This makes the whole Lambda setup risk-free. You can swap between Lambda and your app running on EC2 or pick hand-pick a few routes to pass to Lambda.

I would recommend letting the app run for a few days in production to get comfortable with it and its billing, configuration and deployment. The experience over those few days will let you refine your Lambda template and strategy. This Lambda template will be your base for any further Lambda excursions.

Deployment Strategy

Claudia.JS has a good breakdown of AWS Lambda versioning and deployment.

In summary, each Lambda code-push gets its own incrementing release number (21, 22, 23, …). We can point production to 22 and development to 23 or whatever. Then we can point dev.foo.com to the development version and api.foo.com to the production version via our ALB and “Target Groups” configuration. This flexibility is very convenient and I highly recommend it.

Combined with path based routing, this is a winning combination.

But my monolithic app is written in PHP/Django/Etc

Node.js uses the v8 Javascript engine. Yes, the same trusty engine that ships with the Google Chrome browser. You’ve likely been working with it for years and will continue to write Javascript for many years to come – there’s no reason to shy away from it now.

However, Lambda also supports Java, Go, PowerShell, C#, Python, and Ruby so if your monolithic app is already decoupled, you can copy parts of it to a serverless setup fairly easily and route them via ALB path-based routing.

PHP is a bit of a wash. It’s possible that with PHP v8, there will be some hope for serverless cloud but I wouldn’t hold my breath.

Start Small

If you are just getting started with serverless, start with the slow-moving parts of your code. Code that you change frequently might be better where you are most comfortable i.e. your monolithic app. In a week or so you’ll know if Lambda is working for you.

Services like a screenshot service or web scrapers are excellent candidates for serverless and will hum along for years with cheap per-invocation pricing.

Once again, using ALB routing features, we can easily point our API endpoint to our Lambda instance or back to our app running on an EC2 instance. This makes the whole thing risk-free.

Keep it Simple

Preferrably, don’t mix your web-scraping service and your screenshot service into a single code-base. This will give you freedom to modify your microservice independently of other microservices. This will also give you independent release cycles for your services.

Resist your inner sysadmin unless there is a good reason to upgrade (performance or security). If one service uses Node v6 and another, Node v10, that’s okay. That’s because each Lambda service is autonomous. It is liberated from the release cycle of your monolithic app. This is something you should benefit from. On the other hand, if you want to update your lambda services to the latest stacks as fast as you can, that’s okay too. Just assess the need first.

Conclusion

AWS Lambda is great if you can find the right balance. Don’t go overboard with microservices or splitting out your code. ALB gives us a good pricing balance. Having the ability to run the code on Lambda, on an EC2 instance or on your local machines makes life easy. Path-based routing makes it pain-free to move things around between your monolithic app and your Lambda functions in the early days.