Microservices is one of those buzzwords that generate a lot of hype but is ill defined. In another post, I comment about defining microservices. In this post, my main goal is to discuss pros and cons of using microservices in contrast with the monolithic approach, so let's cut to the chase.
What you gain and what you lose
Benefits of using microservices:
- Deployability: more agility to roll out new versions of a service due to shorter build+test+deploy cycles. Also, flexibility to employ service-specific security, replication, persistence, and monitoring configurations.
- Reliability: a microservice fault affects that microservice alone and its consumers, whereas in the monolithic model a service fault may bring down the entire monolith.
- Availability: rolling out a new version of a microservice requires little downtime, whereas rolling out a new version of a service in the monolith requires a typically slower restart of the entire monolith.
- Scalability: each microservice can be scaled independently using pools, clusters, grids. The deployment characteristics make microservices a great match for the elasticity of the cloud.
- Modifiability: more flexibility to use new frameworks, libraries, datasources, and other resources. Also, microservices are loosely-coupled, modular components only accessible via their contracts, and hence less prone to turn into a big ball of mud. Dynamic discovery and binding via a registry (e.g., Apache ZooKeeper, Netflix Eureka) is sometimes used for location transparency.
- Management: the application development effort is divided across teams that are smaller and work more independently.
Challenges of using microservices:
- Deployability: deployment becomes more complex with many jobs, scripts, transfer areas, and config files for deployment.
- Performance: services more likely need to communicate over the network, whereas services within the monolith may benefit from local calls. Also, if the microservice uses dynamic discovery, the registry lookup is a performance overhead.
- Availability: if you use a registry for dynamic discovery, unavailability of the registry may compromise the consumer-service interaction.
- Modifiability: changes to the contract are more likely to impact consumers deployed elsewhere, whereas in the monolithic model consumers are more likely to be within the monolith and will be rolled out in lockstep with the service. Also, mechanisms to improve autonomy, such as eventual consistency and asynchronous calls, add complexity to microservices.
- Testability: automated tests are harder to setup and run because they may span different microservices on different runtime environments.
- Management: the application operation effort increases because there are more runtime components, log files, and point-to-point interactions to oversee.
- Memory use: several classes and libraries are often replicated in each microservice bundle and the overall memory footprint increases.
What about autonomy?
Much of what I read about microservices mentions autonomy as a benefit. For example, Sam Newman's Building Microservices book says increased autonomy is a huge advantage of microservice architecture. But what is autonomy? And why didn't I list it in the pros of microservices above (or the cons either)?
First off, there's design autonomy: how much freedom you have to design, implement, and modify a software element. But more important to our discussion is runtime autonomy: how much control a component has over everything that is executed when the component is invoked. A service that doesn't need to interact with any other components outside its boundary is highly autonomous. If a service calls another service, accesses a shared database, sends a message or event to a message broker, then it loses a little bit of autonomy. A design goal is not to get rid of these kinds of interactions, but to avoid problematic ones so that the level of service autonomy doesn't compromise performance or reliability.
And what about the autonomy of microservices? They certainly beget high degrees of design autonomy--the team can employ different technologies and patterns to design and implement each microservice, and can change and redeploy each microservice independently. On the other side, services within a monolith often have to follow the same design constraints and change+deploy cycles--less design autonomy.
As for runtime autonomy, I'm afraid the very idea of microservices goes in the other direction. We're splitting the monolith into small distributed services. The overall business logic that was once collocated in the monolith is now spread across microservices. It's more likely that a microservice will interact with other microservices over the network--that interaction decreases autonomy. If the interaction between microservices involves changing data, the need for a transactional boundary further compromises autonomy. To quote Newman, "Don't also be surprised if things like distributed transactions or CAP theorem start giving you headaches."
The good news is that much of the schooling about microservices I've seen emphasizes this apprehension with autonomy. To avoid autonomy issues, the experts advise you to employ techniques such as eventual consistency, event-driven architecture, CQRS, cache (data replication), and aligning microservices with DDD bounded contexts. My point is that these great techniques are not inherent to microservices, but are desirable to prevent service autonomy issues that are inherent to microservices.