Microservices are great architecture for various IT areas. It supports the successes of many organizations, but on the other hand is often subject to quite strong criticism. How to avoid possible problems and properly use the mechanisms provided by microservices?
The article consists of three parts – in the first part (this one) we will look at the features, postulated in relation to microservices, connected with the division of the application into small components. In the second (soon to be published) we look at the features concerning the independence of individual modules. In the third (published next) we will look at how initial expectations for microservices are actually realized.
First, let's try to determine why we want to introduce microservices at all.
Business expects efficient IT in the organization. This means that IT is to be able to support the growing number of customers, to keep the solution and data security, support for building new functions – in the field of innovation or integration with partners, and this all while maintaining cost effectiveness.
Moving from a strategic to a more down–to–earth level, if we have a relatively small solution, working in a small organization, the implementation of the above goals seems possible in any architectural model. However, when an organization grows and with it, the level of solution complexity increases, meeting the above expectations becomes difficult.
One of the architectural styles to help maintain efficient IT in larger organizations is the architecture of microservices. Within this model we create a system based on cooperation of small, independent and isolated components. Individual microservices are oriented around business capabilities, work in separate processes and on separate data, can be delivered, updated and scaled separately. They are loosely coupled with other microservices and communicate only through documented APIs.
Let's take a look at the features of the solution that are proposed in the microservice architecture.
Application as a set of small components
An application based on microservices should consist of a set of small, cooperating modules – referred to as microservices. Each of these modules should be "small". What does it mean? How big should such a module be?
In general, the maximum number of functions provided by a given module is not specified anywhere in numbers – it should not be the main criterion for division. Componentisation is more important. That is, an attempt to organize services in such a way, that the functions of the given microservice module are interested in the same area, operate on the same data and change for the same reason. If the above requirement is met, then the correct microservice will be both the one containing one function and several dozen (although it is rather rare). The size should always be assessed in the context of the pros and cons in a given usage context.
- Clear boundaries between modules – individual changes in one area should not affect other areas.
- Small module is easier to maintain – it is simpler to understand and change it.
- Programmers can be more productive, they do not have to analyze the entire solution with each change, they just need to understand the logic of the module.
- Easier to evaluate the test coverage and the tests take less time.
- Small components are easier to build and deliver to production.
- Division of the system into many modules allows multiple teams to work in parallel.
- This gives you the chance to provide solutions faster.
- Errors occurring in a single module do not have to affect the operation of the entire system.
- Starting small modules is quicker, which improves the dynamic scaling of the solution.
- Small modules are better suited for native cloud solutions, where dynamic environments with automated deployment and scaling are promoted.
- Individual modules communicate with each other via a network – we have more overhead for data serialization / deserialization and communication in general.
- Higher risk of some failures – in particular every network communication carries the risk of error.
- A single failure does not have to mean a system failure, but unfortunately there is still such risk – due to cascading depletion of resources. Defensive mechanisms are necessary – such as Circuit Breaker or Bulkhead.
- Problems arising from a high number of modules: larger logs, more difficult monitoring, more demanding tracing and a generally more complicated working environment.
- Most solutions require integration of services – a difficult process both in terms of management and maintenance – different teams have to agree and share responsibility.
- Focusing on individual modules creates a risk of losing or decreasing quality of knowledge about the concepts around which the whole system is built.
- Ensuring the availability, validity and consistency of service documentation (not only API documentation) – this will facilitate integration between services and allow for clear definition of responsibilities.
- Emphasis on maintaining and transferring knowledge about the architecture of the entire system as well as the competences and responsibilities of individual modules.
- Division of microservices into certain areas of management – higher–order structures (a man cannot think of hundreds or thousands of specific objects at the same time).
- Individual approach to each microservice – requirements for quality, security, speed and frequency of deployments can and should be different, depending on the application.
- Care for the actual independence of microservices.
Business capabilities as an axis of service organization
How, specifically, do you split the microservices? The most recommended division is by business areas. Generally, the point is to include in a given module those functions, whose need for existence and reasons for change are common. Changes in systems are usually commissioned by business, so it is natural, that it is better to operate in one place than in many.
- Fewer places requiring change – fewer teams implementing change – less overhead for coordination.
- We avoid creating information silos dedicated to a given technical area.
- Simplifying project communication.
- Improved communication and coordination means faster delivery of solutions.
- This model is sometimes difficult to introduce, due to somewhat natural tendencies to group people with similar interests.
- Modules from different layers, in particular frontend, use different concepts, have different responsibilities and different ways of implementation.
- It is difficult to keep the abstractions of individual layers of the system, which can be useful in ensuring high independence of the services.
- Risk of duplication of functionality – in many business modules we implement the same logic.
- Pure business division is not always possible – for example in the case of specific technical solutions that require unique competences, such as AI, or in cooperation with external systems, where their specificity requires the use of logic, that we do not want to spread.
- From an architectural perspective, the division of the system into layers is of great value – it provides encapsulation and separation of interests – let's not try to remove it, but rather reconcile with the business responsibilities of the teams.
- The DDD – Domain Driven Design methodology can help to separate functional areas. It requires specifying a universal language – Ubiquous Language and defining limited contexts – Bounded Contexts, within which we can define the models and logic of system's work. This approach focuses on avoiding misunderstandings resulting from different interpretations of the features of a given object and allows, very importantly, to clarify the concepts used in communication.
- A given business area can be divided into several modules – each responsible for a different processing layer.
Let's take a banking functionality, where the system logic is divided into such business areas as e.g. customer authentication, provision of account balances, account history service, transfer order, loan application. Each of these areas should be a separate microservice or several microservices. Depending on the requirements of the central banking system, which we assume is outside the architecture of microservices, and cross–cutting issues related to for example security, the system may have a layered structure. Individual layers are responsible for communication with external systems, ensuring authentication and authorization of operations, validation and analysis of input data, integration and processing control.
In such a model, it is important to avoid modules covering different business domains – the transfer should be in a different microservice than the loan application or history retrieval, although it can integrate with these modules – e.g. to make a transfer you need to know the balance of the source account. Integration should take place within strictly defined concepts related to the business domain of the target microservice, not the microservice initiating integration. So, for example, we can ask the microservice that takes care of the accounts about the account balance, but not whether we can make a given transfer.
The same applies to security issues, although we have two options here:
A. The module, e.g. related to ensuring authorization of operations or control of transaction limits, will be divided into microservices dedicated to various business areas (e.g. separate for authorization of transfers, separate for authorization of credit applications).
B. The module will provide a generic API allowing parametric authorization of any type of operation.
In the latter model, it is important to construct such a solution so that, for example, when extending a transfer operation with a new parameter, no changes in the generic operation authorization microservice are necessary. This would mean a tight coupling and generally limit the benefits offered by microservices. To avoid this, the generic API should be really generic – allow, for example, to parameterize the content of the SMS confirmation, generated by the authorization module, or to register new types of authorized operations from outside, together with their approval schemes.
The choice of the specific model is in each case an individual matter. The model with separate microservices in the authorization layer has this advantage, that the responsibility for solution security is more concentrated, the model with generic authorization microservice facilitates changes and accelerates up the delivery of solutions.
Small microservices should also mean small, independent teams that deal with them – often the size of the microservice is defined precisely by the size of the team, that is to deal with it. It is most often recommended that the size of the team be limited to 6–7 people.
It is often pointed out that a team should be built around a business issue – a product, and not within the shared competencies of team members – the so–called vertical split. It is postulated that the responsibility of a given team should also include frontend modules (at the application level in the browser or on the phone). Then a single group of people has a full set of competences that allows themselves to make any changes in the logic and implementation of services.
Does this mean one team per microservice? This is what the most common recommendations look like. In our opinion, not necessarily. We recommend an individual approach, starting from the other side – from the assumed size of the microservice, the frequency of changes introduced in it, the significance of a given module for the whole solution and balancing all pros and cons. Only then can we determine the size and competence of individual teams.
- Possibility of parallel work.
- A small team also has small communication barriers.
- If the team has specialists from various areas of competence, it is easier to make changes – they all have one goal and one schedule.
- A sense of responsibility for the product (business functionality) in the team – better quality of the solution on production.
- A team dedicated to each microservice is a considerable cost and organizational challenge if specialists from various fields are to be in each team.
- Not very effective division of workload within a team and between teams – business changes do not go to all modules at an equal pace.
- Risk of isolating employees dealing with different microservices – difficulties in communication between teams.
- Vertical division carries the risk of excessive coupling of different layers, e.g. spreading the concepts from backend to frontend, what reduces encapsulation and makes it de facto more difficult to implement changes in the long run.
- The concept of a small independent team often is associated with limiting documentation or data validation.
- Consider the idea of several microservices as the responsibility of one team.
- Maintain, as far as possible, business rather than technical division in terms of team responsibility.
- Focus on maintaining actual module independence, comprehensive and complete documentation, and using only loose coupling of modules – especially if several of them are maintained by the same team.
Automation of testing, delivery and implementation process
If we have microservices in a larger system, we also have the situation of a large number of independent modules. Attempts at manual management in such an environment will result in errors and long operation times. The solution is automation.
Automation should cover the entire software development process: from verification of code quality, through automation of development builds, unit, integration and system tests, preparation of delivery, container and configuration artifacts, to the very process of passing changes between environments to production launch. In addition, the maintenance of the solution should also be automated: log processing, runtime monitoring, tracing, performance analysis.
- Reduces the risk of errors resulting from manual operation.
- Allows for continuous delivery and frequent deployment of large, complex applications.
- Speeds up delivery times.
- Allows a large number of (even parallel) deployments.
- Starting automation processes requires a lot of work and organizational effort.
- Maintaining the efficiency of software production lines is a continuous effort.
- Every business change can cost more – it requires updating existing ones or adding new automatic tests.
- It is necessary to use many additional tools – this complicates the solution and requires building appropriate competences in the team.
Despite the difficulties and costs of implementation and maintenance, automation is needed in every more complex system. In the microservice model it is practically indispensable.
End of the first part.
Part two, focusing on the independence of microservices soon.