Unfortunately, I have no experience with microservices, but about a year ago I was very actively interested in this topic and studied all the sources of information that I could find. I looked at a few speeches at conferences, read a few articles from very reputable and experienced experts like Martin Fowler, Fred George, Adrian Cockroft and Chris Richardson to learn more about microservices. This article is the result of my research.
Microservice architecture is an approach to creating an application, implying a rejection of a single, monolithic structure. That is, instead of executing all the restricted application contexts on the server using in-process interactions, we use several small applications, each of which corresponds to some limited context. Moreover, these applications run on different servers and interact with each other over the network, for example, using HTTP.
In other words, we encapsulate certain application contexts in microservices, one for each, and we turn microservices themselves on different servers. ')
SOA and microservices
According to Martin Fowler, the term SOA is abused by all and sundry, today it means a lot of things. From Martin’s point of view, microservices are a kind of SOA .
When should I use microservices?
As a theoretical architect who wants to become a practitioner, I consider the following. When deciding whether to use microservices, in no case can one be guided by myths, or the desire to “try it next time,” or the desire to be at the forefront of technology. This, in accordance with the conclusions of Rachel Myers , must be approached exclusively from a pragmatic point of view. Rachel notes that architecture should :
make the product more flexible and resilient to failure;
facilitate understanding, debugging, and code modification;
help in teamwork.
I agree with Rachel, but I also believe that monolithic architectural schemes satisfy these criteria.
Monolithic architecture is much easier to implement, manage and deploy.
Microservices require careful management, as they are deployed on different servers and use the API.
Partial Deployment
Microservices allow you to update the application in parts as needed.
With a single architecture, we have to re-deploy the entire application, which entails much more risks.
Consistency
With a monolithic architecture, it is easier to maintain the consistency of the code, handle errors, etc. But microservices can be completely controlled by different teams in compliance with different standards.
Availability
For microservices, accessibility is higher: even if one of them fails, it does not cause the entire application to fail.
Intermodular refactoring
A single architecture makes it easier to work in situations where several modules must interact with each other or when we want to move classes from one module to another. In the case of microservices, we must very clearly define the boundaries of the modules!
Preservation of modularity
Maintaining modularity and encapsulation may not be easy, despite the SOLID rules. However, microservices allow you to guarantee the absence of common states (shared state) between modules.
Multiplatform / heterogeneity
Microservices allow you to use different technologies and languages, in accordance with your tasks.
Personally, I like the pragmatic approach of Eric Evans . From the point of view of hardware resources, microservices have advantages that common architectures do not have, and also facilitate the solution of a number of tasks from a software point of view:
Microservices
Hardware advantages
Software Benefits
Independent scalability
When placing modules on separate server nodes, we can scale them independently of other modules.
Preservation of modularity
Both single and microservice architectures allow you to maintain modularity and encapsulation. However, this can be quite a difficult task, which will take decades to complete, despite the SOLID rules. But microservices allow you to provide a logical division of the application into modules due to the explicit physical separation of the servers. Physical isolation protects against violation of the limits of limited contexts.
Independent technical stack
Due to the distribution of modules across different server nodes and an independent interaction language, we can use completely different programming languages, interaction tools, monitoring and data storage. This allows you to choose the best and most convenient solutions, as well as experiment with new technologies.
Independent evolution of subsystems
Microservice can develop and break backward compatibility without burdening itself with support for older versions, since you can always leave the old version of microservice running the necessary time.
I believe that the main reasons for using microservices are hardware advantages that cannot be achieved with a single architecture. So if the above points are important to you, then microservices have no alternative. If the hardware advantages are not critical for you, then the complexity of the microservice architecture may outweigh its advantages. It also seems to me that with the help of a single architecture, it is impossible to achieve partial deployment and partial accessibility, typical of microservices. These are not key benefits (although these are benefits anyway).
Regardless of our tastes and wishes, it is IMPOSSIBLE to start a new project right away using microservice architecture. First you need to focus on understanding the task and on the way to achieve it, without spending resources on overcoming the enormous complexity of creating an ecosystem of microservices ( Rebecca Parsons , Simon Brown ).
Prerequisites
Continuous deployment
Opportunity and focus on constant acceleration
One of the reasons for using microservices is that we want to be able to change something quickly in order to respond to changes in business requirements, be ahead of the competition. Or, in the words of Eric Evans, we need to be aware of the chaos in companies :
The reality of software development is that at first we never have a complete understanding of the problem. Our understanding deepens as we work, and we constantly have to refactor. So, refactoring is a need, but at the same time a danger, because the code becomes more confusing, especially if you do not respect the limited contexts. Microservices force us to observe the limits of limited contexts, which allows us to preserve the efficiency, clarity, isolation and encapsulation of the code in separate communication modules. If the module / microservice becomes entangled, then this entanglement only remains in it, and does not extend beyond its limits.
We need to act faster at all stages of development! This is true for any architecture, but microservices are more convenient in this regard. Martin Fowler says you need to be able to:
Quickly commissioning: quickly deploy new machines for development, testing, acceptance and operation.
Quickly deploy applications: automatically and quickly deploy our services.
Fred George states the same thing: there is a huge need to speed up the work in order to withstand the competition! He gives a retrospective analysis of the time required for commissioning the server, and notes that in the 1990s it took 6 months, in 2010, thanks to cloud services, 30 minutes, and in 2015, Docker allowed to raise and launch a new server in less than in a minute.
Monitoring is extremely important ( Rebecca Parsons ), we need to immediately find out that the server has crashed, that some component has stopped responding, that calls fail, and for each of the microservices ( Fred George ). We also need tools for quick debugging ( Martin Fowler ).
Strong devops culture
We need devops to monitor and control, with close relations and good interaction between them and the developers ( Martin Fowler ). When working with microservices, we have to deploy more, the monitoring system becomes more complicated, the number of possible failures grows. Therefore, a strong devops culture ( Rebecca Parsons ) is very important in the company.
Specifications
Martin Fowler and James Lewis in their well-known article and speeches ( Fowler , Lewis ) provide a set of characteristics for determining microservice.
An architecture based on loosely coupled services with limited contexts. (Loosely coupled service oriented architecture with bounded contexts.)
Limited context is the concept of explicit boundaries around a business context. For example, in the framework of e-commerce, we operate with the terms "themes", "payment service providers" (payment providers), "orders", "shipping", "application store". These are all limited contexts, which means candidates for microservices.
Useful general information about microservices is provided in Sam Newman’s book Building Microservices. According to James Lewis, microservices should :
cheap to replace;
scale fast;
be resilient to failure;
in no way slow down our work.
How big is microservice?
James Lewis argues that the service must be “ large enough to fit in your hand, ” that is, so that one person can fully understand his device and work.
There are different opinions about the size of microservices. Martin Fowler describes cases where the ratio of the number of employees and services ranged from 60 to 20 to 4 to 200. For example, in Amazon, the approach with the “two pizzas team” is used: there should be enough people in the microservice team to they could be fed with two pizzas.
Fred George believes that microservice should be “very, very small” in order to be created and accompanied by only one developer. The same goes for James Lewis.
I agree with James Lewis, Fred George and Adrian Cockroft. It seems to me that microservice should correspond to a limited context that one person can fully understand . That is, the wider the functionality of the application, the more microservices should be. For example, in Netflix there are about 800 ! ( Fred George )
However, both at the very beginning of the microservice life cycle and later, the limited context may be too large for one person to understand. It is necessary to identify such situations and split up such services into smaller ones. This is in line with the concepts of evolutionary architecture and DDD, which implies that the architecture is constantly changing / refactoring as it goes deeper into the task and / or changes in business requirements. As Rebecca Parsons says, “crushing is extremely important”: when developing microservices, it is most difficult to determine their boundaries . And while promoting the work, we will definitely unite or split up services.
Component view through services
A component is an element of the system that can be independently replaced, improved ( Martin Fowler ) and scaled ( Rebecca Parsons ).
When developing software, we use two types of components: A. Libraries: pieces of code used in applications that can be supplemented or replaced by other libraries, preferably without affecting the rest of the application. The interaction takes place through language constructs. However, if the library of interest to us is written in another language, we cannot use this component . B. Services: parts of applications that in fact are small applications running in their own processes. The interaction is accomplished through interprocess communication, web service calls, message queuing, etc. We can use the service written in another language, since it runs in its own process (this approach is preferred by Chad Fowler ).
Independent scalability - each service can be scaled independently of the rest of the application.
Heterogeneity
Heterogeneity is the ability to build a system using different programming languages. The approach has a number of advantages ( Martin Fowler ), and Chad Fowler believes that systems must be heterogeneous by default, that is, developers should try to apply new technologies.
The advantages of a heterogeneous system:
Prevents close ties through the use of different languages.
Developers can experiment with technologies, which increases their own value and allows them not to go to other companies to try new items. Rule When experimenting with new technologies: - you need to use small code units, modules / microservices to reduce the risk; - code elements must be disposable.
Organization of human resources in accordance with business opportunities
Sometime within the development teams, groups organized themselves based on the technologies used . As a result, the project was created by a DBA team, a server-side development team, and an interface development team that operated independently of each other. Such a scheme affects the quality of the product, because knowledge in specific areas and development efforts are dispersed into subgroups.
In the microservice approach, teams should be organized on the basis of business opportunities: for example, orders, shipments, catalogs, etc. Each team should have specialists in all the necessary technologies (interface, server, DBA, QA ...). This will give each team enough knowledge to focus on creating specific parts of the application - microservices ( Martin Fowler , Eric Evans ).
The approach is combined with Conway's law , which states that if we need highly connected separate microservices, then the structure of the organization should reflect the desired component structure.
Systems development organizations ... create architectures that replicate interaction structures within these organizations.
Melvin Conway, 1967
Products, not projects
There used to be such an approach: the team creates some kind of functionality, and then transfers it to the support of another team.
In the case of microservices, the team should be responsible for its product throughout its entire life cycle, including development, maintenance and decommissioning. This forms “grocery thinking”, which means a strong link between the technical product and its business opportunities. That is, a direct relationship is created: how the application helps its users expand their business opportunities.
Smart endpoints and dumb pipes
Again, in the good old days, companies used the Enterprise Service Bus architecture (service bus), which forms a communication channel between endpoints and business logic. Then this approach was transformed into a spaghetti box.
The microservice architecture takes business logic to the endpoints and uses simple methods of interaction like HTTP.
Decentralized management
Key decisions on microservices should be made by people who actually develop microservices. Here, key decisions mean the choice of programming languages, deployment methodology, public interface contracts, etc.
Decentralized data management
In the traditional approach, an application has only one database, and many different components of the business logic of the application “interact” within this database: they directly read data belonging to other components from it. It also means that the same degree of data integrity is typical for all components, even if for some of them this is not the best situation ( Martin Fowler ).
With a microservice architecture, when each business component is a microservice, all components have their own databases that are inaccessible to other microservices. Component data is available (for reading and writing) only through the corresponding component interface. Due to this, the degree of data stability varies depending on the component ( Martin Fowler , Chad Fowler ).
From the point of view of Fred George, this is the first challenge on the path to microservice architecture.
Automation: with one click you can deploy multiple servers.
Phoenix servers: quick start and stop.
Monitoring: you can see when something went wrong, and debug.
Crash Insurance (Design for failure)
The servers over which the application is distributed sooner or later fall, especially different nodes. Therefore, the application architecture must be resilient to such failures ( Martin Fowler ).
Chaos monkey is a tool created in Netflix. It allows you to shut down servers to test system resilience to this type of failure ( Martin Fowler ).
Rebecca Parsons considers it very important that we no longer use even in-process communication between services; instead, we resort to HTTP for communication, which is not nearly as reliable. As a result, there will be failures when communicating services with each other, and the system should be ready for this.
Evolutionary Architecture
The architecture of the entire application should not be static, it should be easy to develop in accordance with the needs of the business. For example, you can:
Transform (refactor) a single application into a microservice application, isolating and transferring business logic sets (limited contexts) into separate microservices.
Combine existing microservices, for example, when it is often necessary to simultaneously change different microservices.
Separate existing microservices when you need and have the opportunity to develop them individually or when we understand that separation will seriously affect business logic.
Temporarily add some possibility to the application by creating a microservice that will work for a certain time.
Frontend / backend
There are two approaches to structuring the frontend and backend with microservice architecture:
Spread out all parts of the user interface on microservices and maintain the relationship between the corresponding microservices. This allows you to establish in-process interaction between the front end and the backend. But then it will be very difficult, if possible at all, to maintain UI connectivity. In the case of cross-border changes in the UI, we will have to simultaneously update several microservices, creating relationships and breaking the isolation and independence of microservices provided by the architecture itself. It turns out almost antipattern!
Spread the frontend and backend code bases , leaving the application UI as one, so that they then interact via HTTP. Microservices will be separated from each other, which further separates the front-end and backend. But the UI can be maintained entirely, easily maintaining its connectivity. This structure recommends using Rachel Myers , and as I understand it, this is the only way. In this case, we have two options for interaction between the front end and the backend:
There are many small asynchronous HTTP requests instead of one large, which will exclude the possibility of blocking (this approach is preferred by Chad Fowler ).
One big request to specialized services (gateway / aggregator / cache) that collect data from the entire microservice ecosystem. This reduces the complexity of the UI.
Danger
Need to manage technology flexibility
One of the advantages of microservices is that we can use different technologies to solve the same problem. For example, in each microservice use different libraries for XML parsing or different data security tools. But the opportunity itself does not mean that we should do it. It is possible that the abundance of technology and libraries will get out of control. So choose a basic set of tools and only refer to others when you really need it ( Rebecca Parsons ).
Need to manage interface instability
At the beginning of the development of microservice, its API is particularly unstable. But even at later stages, when microservice is sufficiently developed, we have to change the API, its input and output. Make changes carefully because other applications will rely on the stability of the API ( Rebecca Parsons ).
You need to be sure that the data is consistent.
Microservices have their own data storage. And in many cases, data belonging to one microservice will be partially or fully copied by another, client microservice. When the data at the supplier changes, it initiates an event to start updating the data copied by the client microservice. The event enters the message queue and waits for the client microservice to receive it.
This scheme means that the client microservice will have outdated data until it detects the necessary event. Data is not consistent.
Of course, as a result, the changes will be applied to all copies, and the data will again become consistent. This is called eventual consistency - coherence in the long run. That is, we know that for a short period of time the data remains inconsistent. This effect is important during application development, from the server side to the UX levels ( Rebecca Parsons ).
How to decompose a single application
Starting to create an application, you must initially adhere to a single architecture - because of its simplicity. At the same time, you should try to create it as modular as possible so that each component is easily transferred to a separate microservice ( Rebecca Parsons ). This is combined with the idea of ​​Simon Brown to develop an application as a set of separate components in a single extensible module.
When decomposing a single architecture into a microservice, or into a set of separate components, it is necessary to think about several dimensions in support of our solution:
Each microservice should represent a limited context, from the point of view of the business concept and from the technical point of view. Usually, microservice should include connections between code elements (between data and / or business logic), as well as several connections with external code elements.
Think about business opportunities (Rebecca Parsons: 1 , 2 )
What value streams exist in the organization? Business products? What business services are delivered?
Think about the needs of consumers (Rebecca Parsons: 1 , 2 , 3 )
We can look at the product not only as creators, but also as consumers: what do they want from our service? How will they use it? What do they expect?
Think about interaction patterns
Which parts of the system can use the same data? What business logic will interact more intensively? (Rebecca Parsons: 1 , 2 , 3 )
Is there a single point of failure in architecture due to the rigid dependence of one microservice on many others? ( Rachel Myers )
Think about data architecture (Rebecca Parsons: 1 , 2 , 3 , Rachel Myers )
Services have their own data, they have their own databases, and we need to proceed from the principle of consistency in the final analysis. If two data structures are very dependent on each other, then it may be better to keep them in one microservice, so that you don’t have to create a mechanism for working with consistency in the final analysis.
Think of patterns of interrelated changes (Rebecca Parsons: 1 , 2 , 3 , Rachel Myers )
If it is possible to foresee the simultaneous change of two code elements, then it is better to store them in one microservice in order to eliminate unnecessary efforts to change the API.
Get ready for merging and sharing services ( Rebecca Parsons )
You probably can't always do it on time. But as we gain experience and deepen into a task, we begin to better understand the location of limited contexts. Business will also change, and we will have to quickly adapt to this! So our system should allow to quickly divide and merge microservices.
There may always be a need to make a critical change in backward compatibility (backwards compatibility breaking change). But we can try to do it only as a last resort. In order not to constantly change your service, create it so that the service has to be changed only when really important data changes.
Do not duplicate static files (HTML, CSS, JS, images) in applications and services.
User interface applications must be completely separated from the microservice ecosystem.
Use single knots.
Use immutable deployments: never update software on existing sites.
Convention over configuration priority ( Chad Fowler )
The structure and naming system adopted in your architecture should be the same for the entire microservice ecosystem.
Create a microservice sandbox generator so that you have a starting point with an already defined familiar structure.
Optimizing the interaction between microservices ( Chad Fowler )
Create a basic HTTP REST client library optimized for REST calls, on the basis of which you can build a specific microservice client, it will be used by other microservices. This optimized client should be ported to all languages ​​used in your ecosystem.