Recently I spoke at Microservices Day in Bangalore on the topic, “What Do You Need to Consider When Building Microservices?” I wanted to share my company’s experiences when migrating our platform as a set of microservices. We implemented them using Docker containers and deployed both on Kubernetes and ECS, so some design directions do take containers into account.
Here, in no particular order, are my top five design considerations. I don’t expect any of these to be new, but they are important to think about.
We have been talking about separation of concerns as a architectural pattern for a long time now. This extends the same principle in making each microservice self-contained.
Self-containment can be achieved by various dimensions. The first one is around requirements: Each requirement is such that a microservice can pick it up independently. The second is on teams: Each team independently can take a requirement and build it. The above two have been in practice for sometime under the convention of agile and modular design.
However, the key thing to keep in mind is self-containment with respect to deployment. Each microservice should be “deployable” independently. This helps in bringing the continuous delivery opportunity to each microservice irrespective of its dependencies on other services.
When a microservice is created as a package to deploy—in our case it was a Docker image—it should carry the default configurations such that it can come up with minimal need for configuration. At the same time, allow these defaults to be “inherited” and customized at an installation level or at the tenant level. This helps each version of the microservice to come with new configuration as defaults, and the new configs are either used as defaults in the installation or are available for further configuration at the deployed instance or tenant level.
This design really helped us separate the concern of deploying images with defaults without worrying about the customization done at the target systems.
When a microservice is deployed, it needs to initialize with the contextual information so it comes up appropriately to the context. It needs to be aware of:
- Which services it is dependent on,
- How to discover them,
- How to perform a handshake with these services so it can start communicating from the word go,
- How to test the dependent services to see if they are responding, and
- How to identify alternatives when the necessary services are not responding or providing information via alerts or logs any inability to perform its function.
This one design consideration itself can bring a lot of value in bringing the need for independent deployment to reality.
Storage as a Domain Model: Domain Volumes
Another important need that we found as we deployed our platform as microservices was to ensure the storage mapping is done in such a way that each container gets a slice of the storage that is “necessary and sufficient” for its operation. This helps limit the amount of data a service has access to perform its functions. This also helps in ensuring there is enough security in place to shield the necessary services from not accessing data that it does not need to.
To bring the above need, consider the mapped volumes broken by domain model so it is easier for new microservices to come and participate in the application architecture. Wrap all configurations, metadata and even data into what I call as “domain volumes” to make it easier to design new services.
Image as Build
I have debated this multiple times earlier. I am now becoming more and more confident, having lived through it. Creating images for every “good” build becomes an important consideration to deploy seamlessly, be it for a software as a service (SaaS) or even distributing for on-premises deployments. Consider moving your build process to spit-out an image into your repository when tagged, so everyone can use these images for testing, validating and finally releasing and deploying.
Hope the above makes sense. Thoughts?