After using microservices for a few years I've found that some uncommonly discussed reasons for using microservices yielded major benefits. I also found that some benefits are misunderstood. So what are some major benefits from microservices?
My experience is with Microservices that use the following patterns:
- Decompose by Subdomain: Each Domain-Driven Design subdomain is at least one microservice.
- Database per Service: Each microservice has its own data store that meets its specific needs.
- Saga: Each microservice publishes events whenever its core data changes. And each microservice subscribes to events on other microservices and stores whatever data it needs to accomplish its tasks in its own data store. These provides failure isolation so that if a single microservice is down or unavailable, only that particular service is affected.
- CQRS: Command Query Responsibility Segregation separates the Create, Update, and Deletion functionalities from the Query functionality. This separates the data models and repositories prividing looser coupling between these functionalities.
- Event Sourcing: Changes are stored in an insert-only fashion and rolling-up the changes gives the actual state.
- Service Integration Contract Testing: Have clients of the service test the functionality of the service to verify its functionality.
- Service Instance per Host: Hosting a single service on a single virtualized host
- Serverless Deployment: Hosting a service in a serverless environment (AWS Lambda, Azure Functions)
- Service Deployment Platform: Hosting a service on a platform (AWS Elastic Beanstalk, Azure Cloud Services)
- Messaging: Using a message broker to decouple the message from the caller thereby allowing an asynchronous response (AWS SQS, Azure Storage or Service Bus queues).
- Remote Procedure Invocation: Directly invoking a service and awaiting its response (REST).
Let's say for 10x cost reasons we now want to move our application to a serverless database (such as Serverless AWS Aurora DB) and only pay for the time the database is actually used. If we use the Monolith approach, we'll have to upgrade the entire application. This rarely happens, and when it does it often takes significant effort and testing.
However, if we use the microservice approach we can implement the next microservice using the new database, overcome its nuances, evaluate its benefits in the real-world, and then migrate each older microservice incrementally, in the order of most anticipated positive impact. Doing an incremental approach on a Monolith would be much more complicated (less likely to be as loosely coupled as microservices) and risks bringing down the entire system.
Time changes everything. Two years after developing an Orders subdomain for a canonical online shopping site, how many product owners and/or programmers remember all the nuances it contains in it? If we now must implement third-party fulfillments, wouldn't it be wiser to create a separate service to do so? Wouldn't that lower the risk of breaking the existing working functionality?
I really like Bob Martin's books: "Clean Code", "Clean Coder", and "Clean Architecture". However when I was reading his recently released "Clean Architecture" book he misunderstood how changes to microservices occur. I've also heard this same line of thinking from others, so this is not meant to call him out specifically, just the train of thought he uses. He gave an example of a taxi service now wanting to add a kitten delivery service as part of its service offerings. Then he described how all the microservices the taxi service used would now have to be changed. This was provided as evidence that microservices don't provide the benefit of a reduced scope of change.
But an architect concerned about not affecting the existing business systems would quickly realize that changing all the services would risk downtime in the core business and wouldn't follow that path. Instead new microservices would be created to handle the delivery of kittens. You could do a similar approach in a Monolith by using new assemblies, but this approach is still more likely to cause catastrophic failure in the existing functionality; if the kitten delivery has a major bug that crashes the service it likely will bring down the core business.
With microservices you reduce the risk of new features negatively affecting proven features. As a former colleague of mine likes to say, "You reduce the blast radius."
Ever found a piece of logic that is commonly used is misleading or can be simplified, so you make the change and then start looking at its usage tree only to find it being used in fifty different places under at least four different subdomains? Me too. What did you likely do? Me too. We reverted the change because the risk just greatly outweighed the reward. We knew we couldn't test everything that this change would touch. And trying to figure out exactly which usages we could change and know they would be well tested was also risky.
Microservices greatly reduce this problem. When you see only ten usages and you know that they are limited to this microservice and you have confidence in your unit, integration, end-to-end, and QA tests for that microservice, you'll be confident enough to make this change.
Software Engineering is constantly evolving. Where was objective-C fifteen years ago compared to today? Why was the Service Locator pattern considered good ten years ago, but is now considered an anti-pattern? How often were we using No-SQL databases five years ago? The highest performing software engineers I've worked with are well-read and embrance these changes.
When I've hired Software Engineers it is more common then not to see at most three years at any given company across a 10+ year career. Software Engineers often quit or get bored once an application has released and the technology, patterns, practices, and approaches have settled. They read about the new and exciting things happening in software and see their skills stagnate in comparison.
Microservices offer the opportunity for every major new feature to use more modern technologies, patterns, practices, and approaches. They offer a reason for high-performing software engineers to stay on a long-running project. This is a huge benefit to both the engineer and their employer.
After architecting, designing, engineering, testing, releasing, and supporting Microservices for a few years I've come to embrace their advantages and mitigate their disadvantages. Overall, I think this approach has been immensely successful.
I have seen a Monolith broken into microservices and witnessed engineers change core logic that all engineers knew needed refactored but the risk was too high until the change was isolated in a single microservice. I have seen quicker evaluation of new technologies in new microservices, and then seen these technologies applied incrementally in existing microservices when they were anticipated to make a significant positive impact. I have seen new features in new Microservices unexpectedly fail and yet not affect the core application; and a few hot-fixes later the new features are brought back online.
In short, I've seen engineers much less constrained by past decisions, which has empowered them to focus on the future.
But like all things in software, further study and experience may change my perspective.
There's a subtle but very important difference between how `flex-grow` and other growy-shrinky Flexbox rules behave and how the `fr` unit of CSS Grid works. Here we explore it using a practical example.