There are a lot of misconceptions about the concept of “microservice”. We should even consider whether “micro” is the appropriate prefix.
Table of contents
- What is a microservices architecture?
- Characteristics of a microservices architecture
- But, first of all, what is the proper size of a microservice?
- Independent deployability
- Communicating microservices
- Context mapping: when microservices share part of the domain
- Infrastructure on a microservices architecture
The concept of microservice has been one of the most polemical topics in the last five to ten years in the already polemical development world. In opposition to the monoliths, we've listened they are better, worse, simpler, more complex, not for everyone, for everyone, and all the other options you can think of. In this article I would like to put some context, trying to explain what is a microservice, where it should be used, what are the challenges, and how to implement it in the best possible way.
What is a microservices architecture?
A microservice architecture, in opposition to a monolith, consists of applying the single responsibility principle to the monolith and splitting it into a set of smaller components that, through the use of APIs and messaging communication, can communicate with each other and do different tasks the monolith was doing. So it's the application of the divide and conquers maximum: a set of smaller components working together.
Characteristics of a microservices architecture
In general, the following are characteristics of a microservices architecture:
- Allows multiple languages: if you implement microservices rather than a big monolith, you can use different programming languages for each of the microservices. Which is not necessarily convenient for all situations.
- Independent deployability: if you have several microservices, you should be able to deploy them one by one, without actual dependencies between them. This is a theoretical advantage of microservices that many times is not true in practice; you should pursue this.
- Simpler testability: in theory, as a microservice should be smaller than a monolith, testing it should be simpler.
- Composability: the smaller the units of software, the more composable the system should be. It's easier to reuse microservices than a big monolith.
- Organizational flexibility: if you have different independent pieces -microservices- in your system, is easier to assign them to different teams, so it makes simpler your company team's organization.
- Comprehensibility: the smaller the unit of software, the easier it to understand is its code. A microservice should be, by definition, smaller than a monolith, so it should be easier to understand and reason about.
- Replaceability: is very hard to replace a full monolith. But, isn't it easier to replace a little microservice doing only a few things?
- Scalability: is easier to scale a microservices architecture than a monolith as you can allow more resources to certain microservices rather than the all-or-nothing situation you have with a monolith.
You can read more about the characteristics of a microservice in my dedicated article.
But, first of all, what is the proper size of a microservice?
This is a usual question as micro seems to mean very little. But, from the perspective of a giant monolith, if you split it into ten different services, couldn't we call it microservices? I think so. So, what is the best size for a microservice? In my opinion, each of the bounded contexts of a given domain could perfectly be a microservice. If you go smaller, then you'll end up with hundreds of microservices that will be hard to maintain and monitor; if you go bigger, then you are doing monoliths. Split your domain into subdomains, define the bounded contexts, and use it as the proper size.
This is a very important characteristic of a microservice. A microservice must be independently deployable from the other microservices you may have in your system. If you need to coordinate the deployment of different microservices, then you have more of a distributed monolith than a microservices architecture.
Why? Because if you can't deploy microservices independently, then you renounce to other microservices architecture characteristics. For example, then you can't benefit from the organization's flexibility, as teams are going to depend on each other; if you need to coordinate deployments, chances are you have a certain level of coupling, so testing a microservice will be hard as you will need lots of integration testing; and replaceability and composability are going to suffer also for the very same reasons.
So, if you go to a microservices architecture, you need your microservices to be independently deployable. How? Versioning contracts. Each microservice should have an input/output set of contacts -direct calls of messaging- with versioning so you can deploy a new microservice with a new contract version while you allow requests to the previous contract for a given transition time.
In a monolith, you can directly access any of the parts that compound it. In a microservices-based architecture, you need to call network-bounded components.
There are different ways to communicate microservices, but not all are as good as the others:
- (Don't do) Shared database: some people share databases between microservices, so they use the database as a communication channel. Bad idea, as depending on the database structure will make your microservices depend on each other in the deployment process.
- (Don't do) Shared FTP: is even worse than the previous one because of performance and security reasons, and it's not very usual in microservices architectures, but when different companies share information. It's a nightmare and you should avoid it.
- (Do sometimes) HTTP calls through an API: This is an acceptable way to communicate microservices if you need asynchronous communication. But bear in mind that you will need both specific backward-compatible contracts and circuit-breakers to be safe.
- (Do as much as possible) Messaging queues: you send messages to queues that other microservices listen to asynchronously. This is the better way as you don't need to depend on the availability of the other microservices, like with direct HTTP communication, so no circuit-breakers needed. And it's also more resilient as if you fail to process a message you can recover the system by fixing it and processing it again.
Context mapping: when microservices share part of the domain
When you define the different subdomains of your domain, you often find that some of them intersect with others. For example, if you have an authentication subdomain and a CRM subdomain, you'll find that both users and people have an email. Then, which is in charge of the email field, that is, which is the source of truth of the email?
First of all, you need to understand that, for each bounded context, a field, even with the same name, can have a different meaning. For example, for CRM the email is a contact channel; for authentication, the email is an identification which, in pair with the password, allows you to log into the system. This is interesting because, for example, CRM should check that the given email is a valid one, maybe even sending an email to validate; from the point of view of authentication, maybe having a valid email is not important at all, and the only thing it needs to check is whether it is a unique value.
Second, you probably think about the kind of subdomain we are talking about: while authentication is a generic, supportive subdomain, CRM can be core on many domains; maybe authentication is even an external service where we don't have any control, like auth0. In this situation, the core subdomain use to be the proper one to manage the source of truth. For example, we could create an account first creating the personal information in CRM and then, from authentication, listening to the PersonCreated event of CRM and creating a user with the very same email. Also, authentication should listen to an EmailChanged event on CRM to update the identification value (the email).
Infrastructure on a microservices architecture
As you can imagine, having your traditional monolith split into different components has its challenges in terms of infrastructure. Containerization in general and Docker, in particular, come to help us: using Docker over a Kubernetes cluster is a very usual way to implement a microservices architecture. If you also add an application gateway like Kong for things like an API key issuing or request limitation you'll be even better.
A serverless approach is also possible for part of your microservices architecture, especially all those parts that are triggered by events, as serverless is very well suited to manage this kind of eventually needed resources.
All the articles on Microservices
- Serverless approach in the context of microservices
- The proper size of a microservice
- Monitoring microservices
- Polyglot microservices: additional things to consider
- Characteristics of microservices
- Circuit breakers on Microservices
- Monoliths vs Microservices: attempting to avoid passions
- Communicating microservices the proper way
- Deployment autonomy on microservices