Serverless framework tips and tricks

Gain time and improve code quality

I already talked about Serverless architectures good practices in a previous article:

In this post I want to focus on the Serverless framework and share many tips & tricks that make my life much easier. Sometimes even too easy…

https://read.acloud.guru/acg-faas-and-furious-b9574b6675c5

Prerequisites

Before reading, here are a few points to consider:

  • The examples and snippets are assuming your services are on a monorepo. If you are not, feel free to adapt them to your package management system.
  • Some of the advices may be only for AWS (usage with SSM, IAM policies, …).
  • Knowledge of the serverless framework variables syntax will help.

Create templates

Even though you can organise your code very differently between your services, you will probably naturally start coding them in a similar way. The serverless CLI provides a way to create a new service following a template. This template is generally hosted on a Github repo, but you can also store it on your monorepo and use a local path when creating your service:

serverless create —-template-path $REPO_DIR/lib/serverless/template-node —-path $REPO_DIR/path-to-the-service —-name service-name

That way you can have a ready-to-use service backbone already adapted to all your specificities.

In my company, we have a node template which includes:

  1. Our babel.config.js and webpack.config.js configuration files.
  2. A default package.json including most common dependencies (Babel, Webpack, Serverless core and plugins) as well as our standards scripts (npm run local, npm run test, npm run lint).
  3. A README.md template including a TODO list for new services.
  4. A serverless.yml template with all our specificities:
serverless.yml template

Share a deployment bucket

This feature is kind of hidden in Serverless documentation, still you can use an existing S3 bucket to store Serverless artefacts and stacks. That way, you will not have one bucket created per service.

To do so, simply define the name in the provider.deploymentBucket.name property (see the previous snippet line 9).


Having a shared folder for your services

If you have a monorepo, I really recommend you to avoid code duplication by sharing a folder to store some common resources. In this post it will be in $REPO_HOME/lib/serverless/common. You can store a relative path to this folder in a custom.relativePathToCommon property of your serverless.yml (see usage in the previous snippet).

It will help you to define some global resources like:

  • Global constants
  • Prefixes
  • Common policies

Let’s dive into these 3 categories.

Global constants

In thelib/serverless/common/env.yml file, we can store some parameters values that can either be hardcoded or retrieved from SSM:

  • ${ssm:MY_ENCRYPTED_PARAMETER~true} if the parameter is encrypted.
  • ${ssm:MY_PARAMETER} otherwise.

We usually store parameters like AWS account ID, some tokens, or anything that can be used everywhere. Nothing complicated, but it helps to keep a code readable.

Prefixes

Once again, this is really basic but it can reduce a lot the number of characters of your YAML files.

That way, instead of writing the full ARN every time you need to reference a resource, you can use this prefix and append only the name. For instance:

${self:custom.prefixes.arn.function}:theLambdaName

instead of:

arn:aws:lambda:${self:provider.region}:${self:custom.env.accountId}:function:theLambdaName

When you have a lot of resources to reference, you will see the difference.

Common policies

It is very likely that you are going to use common resources across your services, let’s take the example of a RDS database. We will assume you are storing the parameters (database name, host, password, username and port) on SSM. To access it, you need to allow your service to get and decrypt these parameters. A basic IAM policy, nothing complicated, but you will copy it in all your lambdas that use RDS, and probably mix these statements with other authorisations. Here is how your serverless.yml could look like:

Without the common policy

To fix this, you could create Customer Managed Policies which are done for this purpose. However, you will have to create these policies either manually or in a stack outside your service (because this policy will be shared). Also, if you want to rename a parameter, it may be more complicated because the code needs to be redeployed at the same time that the policy is changed.

Another solution is to continue deploying the policy with your service but defining it in your shared folder. That is in my opinion the most microservices oriented solution, and as well the one we have chosen. Following the same example, here are the common policy file and the new serverless.yml:

Common policy definition
With the common policy

Serverless helpers

Static YAML syntax is enabling lots of possibilities, but you may be limited if you need some dynamic values. A few examples we had are:

  • Get the latest docker image URI from a ECR repository. This could be solved by committing the images values as constants, but I prefer to query them dynamically.
  • Generate a string containing the current date (to name an AMI for instance).
  • Dump the content of a file, for example a UserData bash script, or the code of a lambda when using CloudFormation custom resources. The content could possibly be written in the serverless.yml file, it’s much cleaner to write a JavaScript or a Bash code in a .js or a .sh file.

To deal with this, we created a serverless-helpers.js file in the service folders containing that kind of functions:

Serverless helpers file

Note that the getLastImageUri function is generic and could be written in a shared library. Now, here is how we can use it in our serverless.yml:

How to get dynamic values

Thus you can reuse this values wherever in your configuration.


Conclusion

I do hope you enjoyed the reading, let’s keep in touch in the comment section, my Github or LinkedIn!