For my latest project, I've created a set of AWS Lambda functions each following its own single responsibilities. I've utilized Javascript for my functions since I wanted to take advantage of it's HTML-parsing capabilities.
To simplify navigation between the source code of my lambdas, I've organized them all inside a mono repo available on Github. Originally, each function had its own node_modules folder with necessary dependencies and a dedicated test folder. The project structure looks as below
![Google]()
Let's have a look at two different modules located in two different functions.
And another one
As you might notice, although they are from different modules both of them rely on the same jsdom module. Thus we can conclude that such project structure leads to dependency duplication for different functions.
Another level of duplication comes from deploying scripts. To perform deploy of the function we need to complete the following steps.
- Restore dependencies
- Run linter
- Run tests
- Package function
- Deploy to AWS
To eliminate duplication, we want to restore dependencies and run linter and tests once and for all functions.
There is, however, a way to build and package all the dependencies at once using Lambda layers. This approach lets us package all the dependencies into a separate layer and treat it for our functions as a common runtime.
We’ll reorganize our repository to look like this.
![Modules]()
A couple of things changed.
- We've merged node_modules out of the src
- We've merged tests
- We've merged all separate builds into a single build.yml
Apart from fixing import paths source code largely left untouched. Let's, however, take a closer look at the build and deploy actions since there are a couple of interesting moments here, worth highlighting.
First of all, let’s have a look at a separate action that deploys the layer.
Nothing special is happening here apart from the fact that now we are using AWS lambda publish-layer-version. Now, let’s jump to consuming the deployed layer when we deploy our functions.
Here, a couple of things are worth noting.
First of all, it is how we rely on the deploy layers job.
The thing that might be unobvious to newcomers is how we use secrets: inherit to pass secrets down to the layer deploy action. One might naturally assume that it will infer secrets from the Github storage. However, this is not true, and child action infers secrets from parent workflow.
Another important thing is forcing newly deployed functions to use the latest version of the published layer. We achieve this in two steps.
Step 1. Querying for the latest layer version and storing it inside the environment variable.
Step 2. Using stored value to configure update function configuration.