The boilerplate code required to retrieve secrets from the various AWS services makes serverless applications harder than they should be to build, and test.
Not only are there two services to choose from but they donβt provide a way to provide the value of the secret to a lambda at runtime. We have to choose and do the work to retrieve the secrets ourselves. Outrageous!
Let's look at an example:
I want to create a function that needs to retrieve 2 secrets
- A Stripe key that is stored in Secrets Manager
- An api key for our companies serious business service in SSM Parameter Store
Here's the code using just the AWS SDK to retrieve the values:
const SecretsManager = require('aws-sdk/clients/secretsmanager');
const SSM = require('aws-sdk/clients/ssm');
module.exports.handler = async () => {
// retrieve stripe keys
const secretsManager = new SecretsManager({ region: 'eu-west-1' });
const { SecretString } = await secretsManager.getSecretValue({ SecretId: 'external/stripe' }).promise();
const stripe = JSON.parse(SecretString);
// retrieve api key
const ssm = new SSM({ region: 'eu-west-1' })
const { Value: apiKey } = await ssm.getParameter({ Name: 'sb-api-key' }).promise();
// serious business logic follows π
// ...
}
Other than the fact it's a bit long there are some serious problems with this approach.
- If we want to move the API key to secrets manager we have to change some application code - that's probably why it's still in SSM
- We cannot unit test the business logic here without first mocking out the calls to the SSM and secrets manager.
LaconiaJS solves these problems really neatly. Here's the example solution using LaconiaJS:
Define 2 environment variables using your deployment framework of choice
LACONIA_CONFIG_STRIPE=secretsManager:external/stripe
LACONIA_CONFIG_SB_API_KEY=ssm:sb-api-key
const laconia = require('@laconia/core');
const config = require('@laconia/config');
function seriousBusiness(lambdaInput, { stripe, sbApiKey }) {
// stripe and sbApiKey are passed into the function by laconia
}
module.exports.handler = laconia(seriousBusiness)
.register(config.envVarInstances());
As you can see there is a lot less code, it's more malleable, and it's much clearer how we would test the business logic of this function.
Now when testing the seriousBusiness function we can pass in arbitrary values for the secrets instead of having to connect to production AWS resources.
test('serious business test', async () => {
const input = {};
expect(await seriousBusiness(input, { stripe: 'abc', sbApiKey: '123' })).toEqual({ message: 'Serious Business Complete' });
})
Finally
Even though I am pretty new to LaconiaJS its making serverless development much more productive and happy for me. I feel that I write less code and get to focus more on the business logic whilst using it. If you are want to learn more have a look at their website https://laconiajs.io/
Top comments (2)
Hi Thomas, thank you for your article. How is your experience with laconia js so far?
Excellent!