In 2015, a peak in microservices was reached: there is no conference without a Netflix engineer to sell you a dream, not a week without new magic framework to do it all without asking any question.
Result: a focus on the tools and beautiful stories rather than substantive issues.
It therefore seemed useful to us to review the architectural aspects of microservices, because choosing a style of architecture for an information system has structural consequences on projects lifecycle and company organization.
Before getting into the substance of this subject, it is necessary to clarify two concepts:
- Service stands for business service, ie a group of technical services (REST, SOAP …) that, as a whole, provide a feature that has a business sense.
- Project stands for an IT development project throughout its lifecycle and not only during its “project phase” before switching to maintenance mode.
1) Why microservices: large projects issues
Architecture microservices was invented to solve some of the problems caused by large projects.
Over time, IT projects tend to grow: we extend gradually existing features, we add others, and rarely remove old ones.
While the code and the project extend, a certain number of pain appear:
When the amount of code increases, the code becomes more and more complex. Even with a solid software architecture, interdependencies between different bricks increase over time.
This complexity has two disadvantages:
Scalability and Reliability
As time goes by, when new business functionalities become more complex, the different bricks have more interactions. It doesn’t matter how much we organize code into layers and components, there are always special cases and patches that make things more blurry.
Beyond a certain threshold, it becomes impossible to have in mind a global model of the project.
Even with solid tests foundations, the multiplication of side-effects of each action makes the system less reliable, and it becomes more difficult to add new features and perform proper refactorings.
Improving the scalability of a system may require to modify structural elements of the project.
The bigger a project is, the more costly and risky these interventions become.
The risk is to end up with a system that is impossible to evolve with a new use case.
To capitalize investments and facilitate the management of people, it is normal to want to have consistency between projects of a company: same way of working, same programming languages, same tools.
Each project is invited to follow transverse choices and may deviate depending on its specific features, provided to justify it.
For large projects, the same tension takes place within the same project: to avoid fragmentation, each technical change must be propagated to the entire code.
Over time, the changes become more and more expensive, and it is more difficult to introduce new tools for specific needs.
To meet the new business requirements, we must be able to arrange an area for innovation within the projects.
Because some innovations are implemented by new projects, most are done on existing projects.
Thus, bigger the project is, more critical it is for the company, less we will take risks to change it to test new products or new markets, and gradually the challenges of stability will prevail over the ability to innovate.
2) The idea of the microservices architecture
The problems just described above have long been known, and practices from agile methods and software craftsmanship can limit their impact.
Unfortunately, these recipes require to be constantly rigorous, and even more as a project gets bigger and people change.
So they are often implemented on paper or with flaws, resulting in unmaintainable projects that we drag for years before replacing them.
At this danger, the response of microservices is simple and rather radical: to avoid large projects issues, you only need to have small projects.
So we will limit the size of projects to a few people in order to have feature teams with a maximum size of seven people all-inclusive. By cutting projects and existing teams when necessary to comply.
This is not a question of separating big projects into sub-teams but independent projects: each has its organization, timing, code base and data.
Exchanges between projects are made by services, whether service calls (REST / JSON) or messages.
The cutting is done by business area, grouping services and data types that have strong links and separating them when they are sufficiently independent.
If the typical configuration is a deployment unit per team, the rule is rather to have at most one team per deployment unit. If the business domain is implemented by multiple applications, they will be carried by the same team.
If you do not understand why this is called “microservices”, no worries: it is a buzzword to make the concept attractive.
Likewise, fans of this approach called “monoliths” conventional applications, to highlight their negative side, even threatening.
This architecture is thus at the confluence of several underlying computing trends:
- SOA has highlighted the benefits of services approach;
- Agile and lean startup provided the organizational models teams;
- The industrialization deployments and virtualization enable to lower the costs of production and exploitation;
- NoSQL has relaxed things on the data integrity side.
3) The perks of the microservices approach
Scalability and Reliability
Constrain the size limit of individual cases and allow to have in mind all the behaviors.
Technical debt is kept under control, and the code is thus able to evolve. Go through service calls to communicate with other areas formalizes exchanges.
Interface contracts are then more strict, and it is easier to consider all cases, including cases of errors.
4) Requirements and limitations
If microservices architecture has many advantages, it has many requirements and a certain number of limitations.
The microservices being a variation of the classic SOA architecture, we will find the same characteristics, but with an additional level of criticality.
The system becomes distributed
Conventional architectures make it possible to ensure to have independent states between different applications: everyone is the master of his business field.
When switching to microservices, the system becomes widely distributed. This introduces new particularly difficult issues.
The most complicated case is about transactions: each time a transaction is shared between two applications, we must manage transactions in two phases or manage cancellations. In a system based on services, there is no tool that allows to take it into account in an automated way. We must do it manually at each location of the code.
And even when you can bypass transaction: there are always references to cross-application data, and therefore a management system of asynchronous events or cache to be implemented to ensure data consistency.
Then there is the case of external services unavailability. Because using services of another application means to depend on it. The design for failure approach allows to limit risks but require to have a rigorous engineering.
It is also important to master all the service quality (SLA) for different applications in order not to be surprised.
Eventually the system becomes more difficult to test: integration tests increase, and require to prepare the data and be well equipped to test cases of technical and business errors.
Although the REST approach suggests to handle simple features, there is always a proportion of calls with “value-added” that involve multiple business areas.
Regarding microservices, it means dial calls between several applications.
This has the effect of multiplying cases of errors to manage (problem of distributed systems) and adding network latencies.
In the most critical cases, it becomes necessary to add specific services in different applications or add data caches, causing consistency issues.
With separate projects and thus independent teams, transverse evolutions are harder to implement.
This requires to synchronize different groups or to establish a complex lifecycle versions system.
The problem is even worse when you want to iterate quickly because it requires that everyone synchronizes themselves all the time.
To maintain flexibility, the natural solution is to isolate clusters of other projects by limiting the interconnections between groups (pattern Kingdom / Emissary). The risk is to add a middle management which is not directly linked to projects.
DevOps and provisioning
Multiply applications means multiply the number of deployments and server instances.
To avoid error and excessive additional costs, we need a very efficient workflow in terms of tools and processes with as much automated deployments as possible. This is even more true for tests and POCs where we want temporary environments as sandbox.
Projects quick start and people allocations
Choose people, arrange transfer, establish a budget…: within a traditional organisation, create a new project can take a lot of time and money.
In order to make it possible to have multiple projects each living their own life, it is necessary to industrialize this organizational aspect.
Within a large project, the production capacity can be reallocated between different parties, whereas smaller structures are more sensitive to workload changes. It is therefore necessary to be able to expand or reduce teams without causing too many constraints.
We are not talking about setting up shared pools of developers or moving people as pawns, but to have a certain flexibility.
Maturity operation and monitoring
Highly interdependent services requires :
- a very good monitoring flows tool to find out quickly where problems arise
- great operating maturity because it will multiply outages
- a status state available for services consumers so they can understand where outages come from when they have consequences for them.
Technology and maintaining skills
Technology choices happening in every team in a decentralized way, it is easier to make mistakes: The trade-offs between innovation and sustainability are more difficult. Enable innovation to meet new requirements means accepting to make mistakes sometimes.
There is also the risk of neglecting good development practices because there are fewer challenges and risks.
Finally, smaller applications have more often break periods during which there is no evolution to develop, with for instance a change to TMA mode. In this case, team members are spread elsewhere and the risk of loss of consciousness is important.
Strategy and governance
For large projects related to companies products , strategic vision comes directly from the business. The partners are not many, it is easy to arbitrate between the different demands depending on the weight of each.
With microservices, many technical projects will be away from the business and have numerous interlocutors. Therefore we need a mature organisation in its communication, management of priorities and prioritization mechanisms.
5) Do we need it?
The fundamental SOA approach is to keep control of organisational and business complexity by distributing it.
By separating the projects, the complexity is reduced on some axes in exchange for an extra cost in other places, including having a distributed system.
You can have well organized monolithics, scalable, evolutive…, but it requires strong discipline at all times. Microservices architecture chooses not to take those risks to make sure to keep control.
However, if this is implemented in an unsuitable environment or in a bad way, we will combine disadvantages without enjoying the benefits, and we therefore take much higher risks than in conventional service architecture.
So, do not tell yourself that you need microservices, ask yourself:
- If you have issues that this approach solves;
- If you have necessary requirements, or if you are ready to reach them before starting the migration.
Only in this case ask yourself this question.
And do not forget that an architecture is a tool that we adapt to our need and not a dogma to follow: if what suits you is a hybrid solution taking some microservices ideas and not others, go for it!
6) How do I go there?
Once decided that a microservices architecture is the right solution, we need to find a way to setting it up.
If there is no magic solution, some approaches seem to emerge.
The difficult case: from scratch
The most attractive situation is to create a new system from scratch, nothing to challenge or to manage, this seems the ideal situation.
Unfortunately, build microservices from scratch is the most difficult case:
- It is complicated to determine, so it seems, the limits where we need to cut out the different projects because it is not clear how the system will evolve.
- As we have already seen, the evolutions are more costly because you have to make cross-project refactoring.
Unless to be already mature on a subject, it is better to go for a monolith approach to begin with.
The favorable case: peel a monolith
The most favorable case is the monolith that we “peel”. In reviewing its organisation and its structure, we will outsource pieces to the edge of the system following the cutting lines that emerged naturally.
The goal is not to end up with 50 mini-projects but rather:
- one or several “core” applications of average size, consistent with each other;
- microservices moving around, which are going to move away with time.
This operation is made easier as the initial application is well structured in technical layers, business bricks and that this reorganisation is respected. The best practices of software developments allow to have “microservices-ready” projects. Otherwise, it takes a lot of investigation to extract some parts of the code.
Automated tests are essential to limit risks. In their absence, it is necessary to consider the application as a legacy and use the proper techniques to remove the technical debt.
Before getting into the “cutting” phase, we must examine the data distribution issues: this is the most structural element and can make the operation impossible.
Finally, we must avoid to be dogmatic considering that the operation is necessarily one-sided.
If later, others projects evolution are getting close to each other and more issues arise by separating them than they are solving, we must not hesitate to merge them back. Merge two projects back is not an admission of failure but rather a good sign because it shows that when your business evolves, your information system is able to adapt.
To go further
- How we ended up with microservices sur l’expérience de SoundCloud
- Microservices. The good, the bad and the ugly
- Out of the Fire Swamp – Part III, Go with the flow sur les questions de données
- Introduction to Microservices sur le blog de Nginx
- MonolithFirst par Martin Fowler
- Manœuvre de Conway inversée chez ThoughtWorks
- Domain-driven design