Microservices

From a translator: some likely already read this titanic work from Martin Fowler and his colleague James Lewis, but I decided to translate this article. The microservice trend is gaining momentum in the world of enterprise development, and this article is a valuable source of knowledge, in fact squeezing existing experience with them.

The term "Microservice Architecture" has become popular in the past few years as a description of how to design applications as a set of independently deployed services. While there is no exact description of this architectural style, there is a certain common set of characteristics: organization of services around business needs, automatic deployment, transfer of logic from the message bus to receivers (endpoints) and decentralized control over languages ​​and data.

Microservices is another new term in the busy streets of software development. And although we are usually quite wary of all such new products, this term specifically describes the style of software development, which we find more and more attractive. Over the past few years, we have seen many projects using this style, and the results so far have been very positive. So much so that for most of our colleagues this style becomes the main style of software development. Unfortunately, there is not much information that describes what microservices are and how to apply them.

In short, the architectural style of microservices is an approach in which a single application is built as a set of small services, each of which works in its own process and communicates with the rest using lightweight mechanisms, usually HTTP. These services are built around business needs and are deployed independently using a fully automated environment. There is an absolute minimum of centralized management of these services. By themselves, these services can be written in different languages ​​and use different storage technologies.

In order to start the story about the style of microservices, it is best to compare it with a monolithic style: an application built as a whole. Enterprise applications often include three main parts: a user interface (usually consisting of HTML pages and javascript), a database (usually relational, with many tables), and a server. The server part processes HTTP requests, performs domain logic, requests and updates data in the database, fills HTML pages, which are then sent to the client’s browser. Any change in the system leads to the reassembly and deployment of a new version of the server part of the application.

A monolithic server is a fairly obvious way to build such systems. All the logic for processing requests is performed in a single process, while you can use the capabilities of your programming language to divide the application into classes, functions, and namespace. You can run and test the application on the developer's machine and use the standard deployment process to check for changes before putting them into production. You can scale a monolithic application horizontally by running multiple physical servers behind a load balancer.

Monolithic applications can be successful, but more and more people are disappointed in them, especially in view of the fact that more and more applications are deployed in the cloud. Any changes, even the smallest ones, require the reassembly and deployment of the entire monolith. Over time, it becomes more difficult to maintain a good modular structure, changes in the logic of one module tend to affect the code of other modules. It is necessary to scale the entire application, even if it is required for only one module of this application.

image

These inconveniences led to the architectural style of microservices: building applications as a set of services. In addition to the ability to independently deploy and scale, each service also has a clear physical boundary that allows different services to be written in different programming languages. They can also be developed by different teams.

We do not claim that the style of microservices is an innovation. Its roots go far into the past, at least to the design principles used in Unix. But we nevertheless believe that not enough people take this style into account and that many applications will benefit if they start using this style.

Microservice Architecture Properties


We cannot say that there is a formal definition of the style of microservices, but we can try to describe what we consider to be common characteristics of applications using this style. They are not always found in one application all at once, but, as a rule, each such application includes most of these characteristics. We will try to describe what we see in our own developments and in the development of teams known to us.

Service Sharing

Throughout our stay in the industry, we see a desire to build systems by connecting different components together, in much the same way as in the real world. Over the past couple of decades, we have seen great growth in the set of libraries used in most programming languages.

Speaking about components, we encounter difficulties in determining what a component is. Our definition is this: a component is a piece of software that can be independently replaced or updated.

The microservice architecture uses libraries, but their main way of breaking down an application is by dividing it into services. We define libraries as components that connect to the program and are called by it in the same process, while services are components that are executed in a separate process and communicate with each other through web requests or remote procedure call (RPC).

The main reason for using services instead of libraries is independent deployment. If you are developing an application consisting of several libraries working in one process, any change in these libraries leads to a redeployment of the entire application. But if your application is divided into several services, then changes affecting any of them will require redeployment of only the changed service. Of course, some changes will affect the interfaces, which, in turn, will require some coordination between different services, but the goal of a good microservice architecture is to minimize the need for such coordination by setting the right boundaries between microservices, as well as the mechanism for the evolution of service contracts.

Another consequence of using services as a component is a more explicit interface between them. Most programming languages ​​do not have a good mechanism for declaring Published Interface . Often, only documentation and discipline prevents breaking the encapsulation of components. Services avoid this through the use of an explicit remote call mechanism.

However, using services in this way has its drawbacks. Remote calls are slower than calls within the process, and therefore the API should be less detailed (coarser-grained), which often leads to inconvenience in use. If you need to change the set of responsibilities between components, it is more difficult to do this because you need to cross process boundaries.

In a first approximation, we can observe that services are related to processes as one to one. In fact, a service can contain many processes that will always be developed and deployed together. For example, the application process and the database process that only this application uses.

Organization around business needs

When a large application is divided into parts, management often focuses on technology, which leads to the formation of a UI team, server team, and DB team. When teams are broken down in this way, even small changes are time consuming due to the need for cross-team interaction. This leads to the fact that the teams place any logic on the layers to which they have access. Conway's Law in action.

“Any organization that designs a system (in the broad sense) will receive a design whose structure copies the structure of the teams in that organization”
- Melvyn Conway, 1967

image
Conway's Law in action A

microservice approach to partitioning involves breaking up services into with business needs. Such services include the full range of technologies necessary for this business need, including the user interface, data warehouse and any external interactions. This leads to the formation of cross-functional teams that have a complete set of necessary skills: user-experience, databases and project management.

image
Service boundaries reinforced by team boundaries

One of the companies organized in this style is www.comparethemarket.com . Cross-functional teams are responsible for the construction and operation of each product and each product is divided into several separate services that communicate with each other via a message bus.

Large monolithic applications can also be broken down into modules around business needs, although this usually does not happen. Of course, we recommend that large teams build monolithic applications in this way. The main problem here is that such applications tend to organize around too many contexts. If a monolith covers many contexts, it becomes too difficult for individual team members to work with them because of their large size. In addition, adhering to modular boundaries in a monolithic application requires substantial discipline. Explicitly defined boundaries of microservice components simplifies support for these boundaries.

How big should microservices be?

Although the term Microservice has become a popular name for this architectural style, the name itself leads to an excessive focus on the size of services and the debate about what the prefix "micro" means. In our conversations with those who were involved in breaking software into microservices, we saw different sizes. The largest size was for companies that followed the rule “Team of two pizzas” (a team that can be fed with two pizzas), i.e. no more than 12 people ( approx. transl .: following this rule, I should be alone in the team ). In other companies, we saw teams in which six people supported six services.

This leads to the question of whether there is a significant difference in how many people should work on one service. At the moment, we believe that both of these approaches to building teams (1 service for 12 people and 1 service for 1 person) fit the description of microservice architecture, but maybe we will change our minds in the future. ( note pere .: from the time of the article, many other articles appeared that developed this topic; the most popular opinion is that the service should be so large that it can completely "fit in the head of the developer", regardless of the number of lines of code ) .

Products, not projects

Most of the software development companies that we see use a project model in which the goal is to develop some part of the functionality, which is then considered completed. After completion, this part is transferred to the support team and the project team is dissolved.

Proponents of microservices eschew this model, arguing that the team must own the product throughout its life. The roots of this approach go back to Amazon, the company has a rule " you developed, you and support", in which the development team takes full responsibility for the software in production. This leads to the fact that developers regularly monitor how their product behaves in production and more contact with users, because they have to take on how . at least part of the duties to support

thinking in terms of the product establishes a connection with the business needs of product -. it is not just a set of features that you want to implement this permanent relationship, the purpose of which -. to help users increase their business opportunities.

Con Of course, this can also be achieved in the case of a monolithic application, but the high granularity of services simplifies the establishment of personal relationships between service developers and its users.

Smart receivers and dumb pipes (Smart endpoints and dumb pipes)

When building communications between processes, we have witnessed many times how a substantial part of the logic was placed in the data transfer mechanisms. A good example here is the Enterprise Service Bus (ESB). ESB products often include sophisticated capabilities for transmitting, orchestrating, and transforming messages, as well as applying business rules.

The microservice community prefers an alternative approach: smart message receivers and stupid transmission channels. Applications built using microservice architecture tend to be as decoupled and cohesive as possible: they contain their own domain logic and act more like filters in the classic Unix sense - they receive requests, apply logic and send an answer . Instead of complex protocols such as WS- * or BPEL, they use simple REST protocols.

The two most commonly used protocols are HTTP requests through the resource API and lightweight messaging. The best expression for the first was given by Ian Robinson : "Be of the web, not behind the web."

Teams practicing microservice architecture use the same principles and protocols that the World Wide Web is built on (and essentially Unix). Frequently used resources can be cached with very little effort from developers or IT administrators.

The second commonly used communication tool is a lightweight message bus. Such an infrastructure usually does not contain domain logic - simple implementations like RabbitMQ or ZeroMQ do nothing but provide an asynchronous factory. In this case, logic exists at the ends of this bus - in services that send and receive messages.

In a monolithic application, components work in one process and communicate with each other through method calls. The biggest problem in changing the monolith to microservices lies in changing the communication template. One-to-one naive porting leads to chatty communications that don't work too well. Instead, you should reduce the number of communications between modules.

Decentralized management

One of the consequences of centralized management is the tendency to standardize the platforms used. Experience shows that such an approach limits the choice too much - not every problem is a nail and not every solution is a hammer. We prefer to use the right tool for every specific job. And although monolithic applications can also be written using different languages ​​in some cases, this is not standard practice.

Breaking a monolith into services, we have a choice of how to build each of them. Want to use Node.js for simple reporting pages? You are welcome. C ++ for real-time applications? Excellent. Do you want to replace the database with the one that is better suited for read operations of your component? For God's sake.

Of course, just because you can do something does not mean that you should do it. But partitioning the system in this way gives you a choice.

Microservice teams also prefer a different standardization approach. Instead of using a set of predefined standards written by someone, they prefer the idea of ​​building useful tools that other developers can use to solve similar problems. These tools are usually extracted from the code of one of the projects and shared between different teams, sometimes using the internal open source model. Now that git and github have become the de facto standard version control system, open source practices are becoming more and more popular in company internal projects.

Netflix is ​​a good example of an organization that follows this philosophy. Sharing useful and, moreover, library-tested code on battle servers encourages other developers to solve similar problems in a similar way, leaving the possibility of choosing a different approach if necessary. Shared libraries tend to focus on common issues related to data storage, interprocess communication, and infrastructure automation.

The microservice community values ​​service contracts, but does not like overheads and therefore uses various ways to manage these contracts. Templates like Tolerant Reader and Consumer-Driven Contractsoften used in microservices, which allows them to evolve independently. Checking Consumer-Driven contracts as part of the build increases confidence in the correct functioning of the services. We know a team from Australia that uses this approach to validate contracts. This became part of their assembly process: service is only collected until the moment that meets the requirements of the contract - an elegant way to get around the YAGNI dilemma.

Perhaps the highest point in the practice of decentralized management is the method popularized by Amazon. Teams are responsible for all aspects of the software that they develop, including 24/7 support. Such a deviation in the level of responsibility is definitely not the norm, but we see more and more companies transferring responsibility to development teams. Netflix is ​​another company practicing this. Awakening at 3 a.m. is a very strong incentive to pay great attention to the quality of the written code.

Microservices and SOA

When we talk about microservices, the question usually arises as to whether this is a normal Service Oriented Architecture (SOA), which we saw ten years ago. There is a sound grain in this matter, because the microservice style is very similar to what some proponents of SOA are promoting. The problem, however, is that the term SOA has too many different meanings and, as a rule, what people call “SOA” differs significantly from the style described here, usually due to the excessive focus on the ESB used for integration monolithic applications.

In particular, we saw so many unsuccessful SOA implementations (starting with the tendency to hide complexity behind ESBs, ending with failed initiatives lasting several years that cost millions of dollars and did not bring any benefit), that it is sometimes too difficult to ignore these problems.

Of course, many of the practices used in microservices come from the experience of integrating services in large organizations. The Tolerant Reader template is one example. Another example — the use of simple protocols — emerged as a reaction to centralized standards, the complexity of which is breathtaking .

These SOA issues have led some microservice proponents to abandon the term “SOA,” although others see microservices as a form of SOA, or perhaps the right implementation of SOA. In any case, the fact that SOA has different meanings means that it is useful to have a separate term for this architectural style.

Many languages, many possibilities.

The growth of the JVM platform is one of the latest examples of mixing languages ​​in a single platform. Switching to higher-level languages ​​to take advantage of the use of higher-level abstractions has been common practice for decades. Just like the transition to hardware for writing high-performance code.

However, many monolithic applications do not require this level of performance optimization and the high-level capabilities of DSL-like languages. Instead, monoliths typically use a single language and tend to limit the amount of technology used.

Decentralized data management

Decentralized data management appears in a variety of ways. In the most abstract sense, this means that the conceptual model of the world in different systems will be different. This is a common problem that occurs when integrating different parts of large enterprise applications: the point of view on the concept of “Customer” among salespeople will differ from that of the technical support team. Some attributes of the “Client” may be present in the context of sales people and may not be in the context of technical support. Moreover, attributes with the same name can have different meanings.

This problem occurs not only in different applications, but also within the framework of a single application, especially in cases where this application is divided into separate components. The concept of Bounded Context solves this problem well.from Domain-Driven Design (DDD). DDD suggests dividing a complex subject area into several contexts and building relationships between them. This process is useful for both monolithic and microservice architectures, but there is a natural connection between services and contexts that helps clarify and maintain context boundaries.

In addition to the decentralization of decision-making on domain modeling, microservices also contribute to the decentralization of data storage methods. While monolithic applications tend to use a single database to store data, companies often prefer to use a single database for a whole set of applications. Such decisions are typically triggered by a database licensing model. Microservices prefer to enable each service to manage its own database: how to create separate instances of a common DBMS for a company, and use non-standard types of databases. This approach is called Polyglot Persistence . You can also use Polyglot Persistence in monolithic applications, but this approach is more common in microservices.

image

The decentralization of data responsibility among microservices has an impact on how that data changes. A common approach to changing data is to use transactions to ensure consistency when changing data across multiple resources. This approach is often used in monolithic applications.

This use of a transaction ensures consistency, but leads to a substantial amount of time on th dependence (temporal coupling), which, in turn, leads to problemamm when working with multiple services. Distributed transactions are incredibly difficult to implement and, as a result, the microservice architecture emphasizes coordination between services without using transactionswith an explicit indication that the consistency can only be final (eventual consistency) and the problems that arise are solved by compensation operations.

Managing inconsistencies in this way is a new challenge for many development teams, but this is often consistent with business practices. Often, companies seek to respond as quickly as possible to user actions and have processes that allow canceling user actions in the event of an error. A compromise is worth it as long as the cost of fixing the error is less than the cost of losing business when using scenarios that guarantee consistency.

Standard, proven in battle, vs imposed standards

Teams using microservice architecture tend to avoid the strict standards set by teams of system architects. They also tend to use and even promote open standards such as HTTP and ATOM.

The key difference is how these standards are developed and how they are implemented. Standards managed by groups like the IETF only become standards when there are several implementations in successful open-source projects.

This distinguishes them from standards in the corporate world, which are often developed by groups of people with little experience in real development or have too much influence exerted by vendors.

Infrastructure automation

Infrastructure automation techniques have evolved greatly over the past few years. The evolution of the cloud in general, and AWS in particular, has reduced the operational complexity of building, deploying, and operating microservices.

Many products and systems using the microservice architecture have been built by teams with extensive experience in Continuous Delivery and Continuous Integration . Teams building applications in this way make extensive use of infrastructure automation techniques. This is illustrated in the picture below.

image

Since this article is not about Continuous Delivery, we will pay attention only to a couple of its key points. We want to get as much confidence as possible that our application is working, so we run many automated tests. To complete each step of automatic testing, the application is deployed in a separate environment, which uses automated deployment.

After you invested time and money in automating the process of deploying a monolith, deploying more applications (services) no longer seems so intimidating. Recall that one of the goals of Continuous Delivery is to make the deployment boring, so one application or three does not really matter.

Another area where teams use intensive infrastructure automation is the management of microservices in production. Unlike the deployment process, which, as described above, in monolithic applications does not differ much from that of microservices, their way of functioning can vary significantly.

image

One of the side effects of automating the deployment process is the creation of convenient tools to help developers and administrators (operations folk). Tools for managing code, deploying simple services, monitoring and logging are now fairly common. Perhaps the best example that can be found on the net is a set of open source tools from Netflix , but there are others, like Dropwizardwhich we use quite intensively.

Design for failure

The consequence of using services as components is the need to design applications so that they can work when individual services fail. Any call to the service may not work due to its unavailability. The client should respond as tolerantly as possible. This is a disadvantage of microservices compared to a monolith, because this introduces additional complexity to the application. As a result, microservice teams constantly think about how inaccessibility of services should affect the user experience. Netflix’s Simian Army artificially (simulates) failures of services and even data centers during the working day to test the fault tolerance of the application and monitoring services.

This type of automatic testing in production allows you to simulate the stress that falls on administrators and often leads to work on weekends. We do not want to say that sophisticated monitoring systems cannot be developed for monolithic applications, only that this is less common.

Since services can fail at any time, it is very important to be able to quickly detect problems and, if possible, automatically restore the service. The microservice architecture places great emphasis on real-time monitoring of the application, checking both technical elements (for example, how many requests per second the database receives) and business metrics (for example, how many orders per minute the application receives). Semantic monitoring can provide an early warning system for problem situations, allowing the development team to get involved in the investigation of the problem at an early stage.

This is especially important in the case of microservice architecture, as breakdown into separate processes and communication through eventsleads to unexpected behavior. Monitoring is critical to detecting unwanted cases of this behavior and quickly resolving them.

Monoliths can be built as well as microservices. In fact, that’s how they should be built. The difference is that it is much more critical to know when services running in different processes stopped interacting correctly with each other. In the case of libraries located in the same process, this kind of transparency is likely to be less useful.

Microservice teams, as a rule, create sophisticated monitoring and logging systems for each individual service. An example is a console showing the status (online / offline) of a service and various technical and business metrics: current bandwidth, request processing time, etc.

Synchronous calls are considered dangerous

Each time you have a set of synchronous calls between services, you are faced with the effect of downtime multiplication. The downtime of your system becomes the product of the downtime of the individual system components. You are faced with a choice: either make your calls asynchronous, or put up with downtime. For example, in www.guardian.co.uk, developers have introduced a simple rule - one synchronous call per user request. In Netflix, in general, all APIs are asynchronous.

Evolutionary design

Those who practice microservice architecture usually worked a lot with evolutionary design and see service decomposition as a further opportunity to give developers control over the changes (refactoring) of their application without slowing down the development process itself. Controlling change does not necessarily mean reducing change: with the right approach and toolset, you can make frequent, quick, well-controlled changes.

Each time you try to break an application into components, you are faced with the need to decide how to divide the application. Are there any principles that indicate how to best "chop" our application? The key property of a component is the independence of its replacement or update, which implies the presence of situations where it can be rewritten from scratch without affecting the components interacting with it. Many development teams go even further: they explicitly plan that many services in the long run will not evolve, but will simply be dumped.

The Guardian website is a good example of an application that was designed and built as a monolith, but then evolved towards microservices. The site core is still a monolith, but new features are added by building microservices that use the monolith API. This approach is especially useful for functionality, which is essentially temporary. An example of such functionality is specialized pages for covering sports events. Such parts of the site can be quickly assembled using fast programming languages ​​and deleted as soon as the event ends. We saw a similar approach in financial systems, where new services were added to open market opportunities and removed a few months or even weeks after creation.

Such an emphasis on interchangeability is a special case of the more general principle of modular design, which consists in the fact that modularity is determined by the rate of change in functionality. Things that change together should be stored in one module. Parts of the system that are rarely changed should not be found along with rapidly evolving services. If you regularly change two services together, think about the possibility that they should be combined.

Putting components into services adds the possibility of granular release planning. With a monolith, any changes require rebuilding and deploying the entire application. With microservices, you only need to deploy (redeploy) those services that have changed. This simplifies and speeds up the release process. The disadvantage of this approach is that you have to worry about the fact that changes in one service will break the services accessing it. The traditional approach to integration is to solve such problems through versioning, but microservices prefer to use versioning only when absolutely necessary . We can avoid versioning by designing services so that they are as tolerant of changes to neighboring services as possible.

Is microservices the future?


Our main goal when writing this article was to explain the basic ideas and principles of microservice architecture. We believe microservice style is an important idea worth considering for enterprise applications. Not so long ago, we developed several systems using this style and we know several other teams that use this approach.

The pioneers of this architectural style that we know are companies such as Amazon, Netflix, The Guardian, the UK Government Digital Service, realestate.com.au, Forward and comparethemarket.com. The 2013 conferences were full of examples of companies moving in a direction that can be classified as microservices, for example, Travis CI. In addition, there are many organizations that have long been using what we call microservices, but do not use this name. (Often this is called SOA, although, as we said, SOA can come in many different and often conflicting forms.)

Despite all this positive experience, we do not claim that microservices are the future of software design. And although our experience is still very positive in comparison with the experience of using monolithic architecture, we consciously approach the fact that not enough time has passed to make such a judgment.

Often the real consequences of your architectural decisions become visible only a few years after you made them. We saw projects in which good teams with a strong desire for modularity developed monolithic applications that completely “rotted” after a few years. Many people think that such a result is less likely in the case of microservices, because the boundaries between services are physical and difficult to break. However, until we see a sufficient number of time-tested systems using this approach, we cannot say for sure how mature the microservice architecture is.

There are definitely reasons why someone might consider a microservice architecture not mature enough. The success of any attempt to build a component system depends on how well the components fit the application. It is difficult to understand exactly where the boundaries of the components should lie. Evolutionary design recognizes the difficulty of drawing the right boundaries and the importance of changing them easily. When your components are services that communicate with each other remotely, refactoring is much more difficult than with libraries working in the same process. Moving code between service boundaries, changing interfaces should be coordinated between different teams. Layers must be added to support backward compatibility. All this also complicates the testing process.

Another problem is that if the components are not selected cleanly enough, complexity is transferred from the components to the connections between the components. A false sense of simplicity of the individual components is created, while all the complexity is in places that are more difficult to control.

There is also a team level factor. Newer techniques are generally accepted by stronger teams, but techniques that are more effective for stronger teams are not necessarily those for less powerful development teams. We have seen many cases where weak teams developed intricate, unsuccessful monolithic application architectures, but it will take some time before we see how this ends in the case of microservice architecture. Weak teams always create weak systems, it is difficult to say whether microservices will improve this situation or worsen.

One reasonable argument we heard is that you should not start development with a microservice architecture. Start with a monolith, keep it modular and break it into microservices when the monolith becomes a problem. (And yet, this advice is not ideal, because good interfaces for communication within the process are not such in the case of an interservice message.)

So, we write this with reasonable optimism. At this point, we have seen enough examples of microservice style to realize that it is a worthwhile development path. We can’t say with certainty what this will lead to, but one of the features of software development is that we have to make decisions based on the often incomplete information that we have access to at the moment.

Link to the original article: Microservices

Also popular now: