The 10 commandments of serverless
Feedbacks on Serverless with AWS
Disclaimer: Despite the title, this is not a holy text. I discovered the serverless world in early 2016, when things were much more handicraft than today. Most of these lessons have been acquired by experiencing and failing. Read it, think about it and question it. Things are evolving quickly and some assumptions may be obsolete soon.
#1 You shall think microservices
I have always been a big fan of Lego bricks. This may be why I find myself so much in microservices. This analogy may help to understand all the stakes of this architecture.
Lego pieces are doing one simple thing and you know they are doing it well. Some are really low level and used to build the foundations (bricks), some are really specific to a use case (wheels, doors) and some others are made to help connecting all the parts (cross axes, gears). By combining all those simple Lego elements, you can build lots of specific things like a car or a house.
Consider your microservices as Lego bricks
By building small but reliable components and assembling them to build complex things, you will gain on many angles:
- Easier to test. As the scope of your services is very limited (user management, authorizations, emails, storage, …), you can focus your tests on what the service is really doing and not what it is integrating with. Note that integration and end to end tests will still be necessary.
- Easier to maintain. As long as the interface between your services is well defined, you should be able to update a service without thinking too much about how it could impact other services.
- Easier to handle technical debt. You will not be forced to do long and risky migrations to get rid of your legacy code or technologies. You can start using new practices in your services incrementally, by letting the old and the new way coexist for a while. You will then be able to validate the new methods as well as stabilizing them before applying them globally. It is like doing canary deployment but for your internal infrastructure.
#2 Avoid dependencies between services
This is not new to microservices, but dependencies have a much bigger impact. Having a microservices architecture with dependencies everywhere is an anti-pattern.
You may think splitting your architectures in many pieces is not compatible with making them independent, a little clarification is required: of course your services will call each others and will be interdependent. The dependencies you must avoid are strong dependencies. Your services must only be aware of the interface of other services, and never assume its inner mechanism.
- As much as possible, your microservices should only be available through an API. Sometimes it can make sense to add other interfaces, like a Pub/Sub system (SNS) or a trigger (S3) to avoid adding some useless layer. It's OK to do that way, as long as you keep the same input/output format for all your interfaces so you don't need to assume the request origin in your lambda.
- The way data are stored should only be known by the service itself, and no other service should assume a specific format. That way, you can completely change the inner mechanism overnight. It is one advantage I see using DynamoDB to store your data: your tables will belong to your service, will be deployed at the same time and you will implicitly segregate your data per service, instead of having a single database where any service can access any data.
#3 One language to rule them all
This is of course very subjective, but I believe JavaScript is the best language for the cloud, and here are the reasons:
- Using the same language for frontend, backend, testing and scripting is a really powerful asset. It means all your team members speak the same language, can share code, and can potentially work on anything.
- Modern JavaScript has a really simple and clean syntax. async/await, among other things, revolutionized my daily life. Less code is better! If the last time you have used JavaScript was to do some jQuery in 2010, then you should have a look at this cheatsheet.
- It has the biggest packages repository. You can focus on writing code that will benefit your business not reinventing the wheel.
#4 Organize your code in a monorepo
This debate will probably stay alive for a while, but:
- It helps you thinking globally when developing a feature. You update all your services within a single PR (easier to test and review).
- It makes it easier to share code (at least for developing). There are various ways to share a library:
- By publishing each package on npm. Lerna morepos may help getting some coherence. I personally think this makes a lot of sense for open source projects (see babel, turf), but versioning is constraining and not always necessary.
- By encapsulating your shared logic in a service. I suggest you to read this article for more details.
- By linking a folder locally (using `npm link`). It can have some side effects (more complicated webpack setup), but it is speeding up development time so much that I will probably stick with this option for a while. - It helps building a strong common CI. Microservices architecture makes functional and integration tests a key of your stability. I strongly encourage you to read this article that redefines the testing paradigm for serverless.
Once again, this may not work for everyone. Splitting in a few repositories may be wise depending on the size/number of your team(s)/product(s), as long as you can find the good partitioning criteria.
#5 Apply a naming convention
Of course the microservices architecture gives you some freedom on how each service behaves inside, but still as the number of your services will grow, you will need some consistency. I would suggest naming all your CloudFormation resources (even meaningless resources, like IAM roles) following a pattern, for instance {service-name}-{stage}-{resource-name}
:
- You will easily recognize the service using this resource, as well as resources that aren’t part of any stack and could probably be cleaned up.
- Having the stage in the name makes it possible to deploy the same service multiple times on the same account, so you can easily test features without breaking your environment.
Protip: If you are using a framework, like Serverless, you can assign the prefix ({service-name}-{stage}-
) to a variable to be less verbose in your declarations.
#6 Serverless is not (only) FaaS
The fully managed combo Lambda/APIGateway/DynamoDB/S3 is obviously a really good starting point and should be enough for simple use cases. However you may quickly have longer or more complex processes:
- How to manage states in a stateless environment (like waiting for a manual task)?
- How to reuse your basic functions across your services, and how to chain them within a process?
- How to encapsulate non-serverless tasks (like a Batch job) in a serverless process?
If these questions sound familiar to you, you probably need an orchestrator and should give a try to Step Functions. It is based on the state machine pattern, and includes common blocks (wait, parallel branches, conditions, tasks). I recently wrote a presentation about that service and I invite you to have a look for a good overview.
#7 Equip yourself
I couldn’t recommend you a better framework than Serverless Framework:
- The most popular, has quite a stable behavior.
- It creates all your resources in CloudFormation stacks when using AWS.
- You will find plugins to do almost everything.
- The core is multi cloud providers (currently 8), which help standardizing the serverless architecture (FaaS services are quite similar is working almost the same way regardless of the provider).
You should also keep an eye to Serverless Components, which allows you to build by assembling components rather than configuring a single service. Not sure where it is going yet, but it looks promising!
AWS is also seriously starting to catch up with AWS Serverless Application Model and its CLI. It is obviously only focused on AWS and doesn't offer an ecosystem of plugins yet, but you will benefit from AWS Serverless Application Repository which lets you deploy third party applications in a few clicks.
#8 Adapt your tooling
Regardless of all its advantages, serverless is still early stage and this new paradigm also brings new problematics.
As developers begin to see the value of serverless architecture, a whole new startup ecosystem could begin to develop around it. — Techcrunch
Here are 2 products I have been working with that I highly recommend you to give a try. In addition to be really focused on the serverless paradigm, in contrast with most well known tools, they are also listening to our needs and adapting really fast.
This service aims to answer one of the biggest challenge in the serverless world: observability. As a single request can easily go threw a dozen of entities (FaaS, database, API, State Machine, 3rd party service, …) it becomes extremely hard to track a whole request at once. Epsagon proposes to solve that by adding a simple wrapper in all your FaaS and offers a superb graph visualisation of your transactions.
There are now several monitoring and observability products for serverless and most of them provide nice features around Lambda, but I believe Epsagon is the only one supporting Step Functions. By combining a powerful serverless orchestrator with a powerful serverless observability tool, it is a real pleasure to develop and monitor complex processes.
Microservices architecture can makes your deployments more messy because they will be split in many parts. Indeed a framework like Serverless will help keeping a consistency in the process to deploy (a single command). But when you will have dozens of services and different stages, you may want something more automated to manage your deployments. Seed.run let’s you configure services within a monorepo as well as stages. You can automate deployment (deploy in staging when you merge on develop, deploy in production when you merge on master), or you can keep it manual and trigger a deployment in one click.
#9 Keep It Simple, Stupid (KISS principle)
This principle should be your leitmotiv. You may wonder why it is particularly important here? Building small things will be easier, more testable, more evolutive. But it also implies you will have interactions between different parts of your architecture, whose you may not always know the inner mechanism. But as long as your services respect a predictable and simple interface, it shouldn’t be an issue because you will be able to learn how to use them in no time. If you are not able to explain in a sentence how to use a service, you should probably make it simpler.
#10 Make it easy to create a new environment
We can never say it enough, but automating everything in your deployments is the key. You should never create a resource outside of CloudFormation. It may take more time to develop, but you won't regret it. Here a a few reasons you may have to setup new environments:
- Creating a testing environment from scratch everyday for your nightly tests.
- Creating an environment per developer or per feature.
- Expanding on other AWS regions.
Some resources can be complicated to deploy automatically (custom EC2 AMI, RDS cluster, 3rd party services) and this is where CloudFormation custom resources become useful. Custom resources are requesting a custom endpoint to update the status of a resource, which means you can create a Lambda that will handle the life cycle of your resources. Have a look here to get started, and if you need to create an EC2 AMI after having executed a User Data script, there is a really good ready-to-use stack that will do everything for you (and with a step function).
Also, my advice is to separate your resources in 2 parts:
- What should be part of your infrastructure and should be unique per environment, like your VPC, domains on Route53, maybe RDS (if you choose to share a cluster and create a database per stage). These elements should be rarely updated and there isn't much added value to use a framework for that, so sticking with pure CloudFormation stacks may be the best.
- What is part of your services and could be deployed many times in a single environment (to try a feature, or for a developer environment). As your resources contains the stage in their names it's now easy to make them coexist in the same AWS account.
#11 Have fun coding awesome things
Oh wait, is it already the 11th commandment? It doesn't matter, serverless is making your life such cooler that it becomes a duty to push the boundaries.
Thank you for reading this article (and many thanks to Hervé Nivon for the review), I hope it will be helpful! If you went so far, you are curious and here are my top 3 sources of inspiration about Serverless to get further: