Technical Exchange

We provide users with a Technical Exchange platform where we offer solutions to common issues encountered during daily product use. If you have additional questions, please reach out to us through the Contact Us section, and we will do our best to address any concerns.

English-Entry >>>

Advantages and disadvantages of microservice architecture

Developing monolithic applications

Suppose you are about to develop a taxi scheduling application to compete with Uber and Hailo. After initial meetings and requirements analysis, you might start the new project manually or using generators based on Rails, Spring Boot, Play, or Maven. Its hexagonal architecture is modular, as shown in the following diagram:

The application core is the business logic, implemented by modules that define services, domain objects, and events. Surrounding the core are adapters that interface with the outside world. Adapters include database access components, messaging components that produce and consume messages, and web modules that provide API or UI access.

Although the logic is modular, it is ultimately packaged and deployed as a monolithic application. The specific format depends on the language and framework. For example, many Java applications are packaged in WAR format and deployed on Tomcat or Jetty, while others are packaged as self-contained JARs. Similarly, Rails and Node.js applications are packaged as hierarchical directories.

This development style is common because IDEs and other tools excel at building simple applications. These applications are also easy to debug: simply run the application and use Selenium to connect to the UI for end-to-end testing. Monolithic applications are also easy to deploy: just copy the packaged application to the server and scale by running multiple copies behind a load balancer. In the early stages, these applications work well.

Drawbacks of monolithic applications

Unfortunately, this simple approach has significant limitations. A simple application will gradually grow over time. In each sprint, the development team faces new stories and writes substantial new code. After a few years, the small, simple application becomes a massive monolith. Here is an example: I recently spoke with a developer who was building a tool to analyze dependencies between JAR files in an application with millions of lines of code. I am quite sure this codebase is a monolith that many developers built over years of effort.

Once your application becomes a large and complex monolith, the development team will struggle. Agile development and deployment become extremely difficult. The primary problem is that the application is too complex for any single developer to fully understand. As a result, fixing bugs and correctly adding new features becomes very difficult and time-consuming. Additionally, team morale declines. If the code is hard to understand, it cannot be modified correctly. It ultimately becomes a massive, incomprehensible mess.

Monolithic applications also slow down development. The larger the application, the longer the startup time. For example, a recent survey showed that application startup times sometimes exceed 12 minutes. I have also heard of applications requiring 40 minutes to start. If developers need to restart the application frequently, most of their time is spent waiting, severely impacting productivity.

Furthermore, large and complex monolithic applications are not conducive to continuous development. Today, SaaS applications commonly change many times a day, which is very difficult with a monolithic architecture. Moreover, the impact of these changes is not well understood, requiring extensive manual testing. Consequently, continuous deployment becomes very challenging.

Scaling monolithic applications becomes very difficult when different modules compete for resources. For example, one module may perform CPU-intensive logic and should be deployed on AWS EC2 Compute Optimized instances, while another in-memory database module is better suited for EC2 Memory-optimized instances. However, since these modules are deployed together, hardware choices require compromise.

Another problem with monolithic applications is reliability. Because all modules run in a single process, any bug in any module, such as a memory leak, can potentially crash the entire process. Moreover, since all application instances are identical, this bug will affect the reliability of the entire application.

Finally, monolithic applications make it very difficult to adopt new frameworks and languages. For example, imagine you have two million lines of code written with the XYZ framework. Switching to the ABC framework would be extremely expensive in both time and cost, even if the ABC framework is better. This becomes an insurmountable barrier, and you are stuck with your initial choice.

To summarize: you start with a successful, business-critical application that eventually becomes a massive, incomprehensible monolith. Using outdated and inefficient technology makes it difficult to hire talented developers. The application cannot scale, reliability is poor, and ultimately agile development and deployment become impossible.

So how do you deal with this?

Microservice architecture - handling complexity

Many companies, such as Amazon, eBay, and Netflix, have solved the above problems by adopting the microservice architecture pattern. The idea is not to develop one massive monolithic application, but to decompose the application into small, interconnected microservices.

Each functional area of the application is implemented using a microservice. Additionally, the web application is split into a series of simple web applications (for example, one for passengers and one for taxi drivers). This decomposition makes deployment easier for different users, devices, and specific application scenarios.

Each backend service exposes a REST API, and many services also consume APIs provided by other services. For example, driver management uses a notification service to inform drivers of a potential ride request. The UI service invokes other services to update web pages. All services communicate using asynchronous, message-based protocols. The internal mechanisms of microservices will be discussed in subsequent articles.

Monolithic applications can also adopt this product mindset, but finer-grained services make it easier to create the relationship between developers and users.

Some REST APIs are also exposed to mobile applications used by passengers and drivers. These applications do not access backend services directly but instead route messages through an API Gateway. The API Gateway handles load balancing, caching, access control, API billing and monitoring, and other tasks. It can be conveniently implemented using NGINX, which will be covered in subsequent articles.

The microservice architecture pattern corresponds to the Y-axis of the Scale Cube shown in the diagram above, a three-dimensional scaling model described in the book "The Art of Scalability." The other two scaling axes are the X-axis, which consists of multiple application copies running behind a load balancer, and the Z-axis, which routes requests to the relevant service.

An application can essentially be represented along these three dimensions. The Y-axis represents decomposing the application into microservices. At runtime, the X-axis represents running multiple instances behind a load balancer to provide throughput. Some applications may also use the Z-axis to partition services. The following diagram illustrates how the trip management service is deployed on Docker running on AWS EC2.

At runtime, the trip management service consists of multiple service instances. Each service instance is a Docker container. To ensure high availability, these containers typically run on multiple cloud VMs. In front of the service instances is a load balancer such as NGINX, which distributes requests across instances. The load balancer also handles other concerns such as caching, access control, API statistics, and monitoring.

This microservice architecture pattern profoundly affects the relationship between applications and databases. Unlike the traditional approach where multiple services share a single database, the microservice architecture gives each service its own database. This approach also impacts enterprise data models. It does mean data duplication, but if you want the benefits of microservices, having a dedicated database per service is essential because this architecture requires loose coupling. The following diagram illustrates the sample application's database architecture.

Each service has its own database. Furthermore, each service can use whichever database type best suits its needs, an approach also known as polyglot persistence. For example, driver management (finding which driver is closest to a passenger) must use a database that supports geospatial queries.

On the surface, the microservice architecture pattern resembles SOA, as both consist of multiple services. However, looking at it from another angle, the microservice architecture pattern is SOA without Web Services (WS-) and ESB. Microservice applications prefer simple, lightweight protocols such as REST rather than WS-, and avoid using ESB or ESB-like functionality internally. The microservice architecture pattern also rejects SOA concepts such as canonical schema.

Each service has its own database. Furthermore, each service can use whichever database type best suits its needs, an approach also known as polyglot persistence. For example, driver management (finding which driver is closest to a passenger) must use a database that supports geospatial queries.

Benefits of microservice architecture

The microservice architecture pattern has many benefits. First, it solves the complexity problem by decomposing a large monolithic application into multiple services. With unchanged functionality, the application is broken down into multiple manageable branches or services. Each service has a clearly defined boundary expressed through an RPC- or message-driven API. The microservice architecture pattern provides a modular solution for functionality that is difficult to achieve with monolithic coding, making individual services easy to develop, understand, and maintain.

Second, this architecture allows each service to have a dedicated development team. Developers are free to choose their development technologies while providing API services. Of course, many companies try to avoid chaos by offering only certain technology choices. However, this freedom means developers are not forced to use outdated technologies chosen at the start of a project; they can choose current technologies. Moreover, because services are relatively simple, rewriting legacy code with current technologies is not a particularly difficult task.

Third, the microservice architecture pattern allows each microservice to be deployed independently. Developers no longer need to coordinate the impact of other service deployments on their own service. This can accelerate deployment speed. UI teams can use A/B testing to deploy changes quickly. The microservice architecture pattern makes continuous deployment possible.

Finally, the microservice architecture pattern allows each service to scale independently. You can deploy each service at the scale that meets its demand. You can even use hardware that better matches the service's resource requirements. For example, you can deploy CPU-intensive services on EC2 Compute Optimized instances and in-memory databases on EC2 memory-optimized instances.

Drawbacks of microservice architecture

Fred Brooks wrote 30 years ago, "there are no silver bullets." Like any other technology, the microservice architecture has its drawbacks. One drawback is related to its name: "microservice" emphasizes service size. In practice, some developers advocate building slightly larger services of 10-100 LOC. While smaller services are preferable, remember that this is a means to an end, not the end itself. The goal of microservices is to effectively decompose the application and enable agile development and deployment.

Another major drawback is that microservice applications are distributed systems, which introduces inherent complexity. Developers must choose between RPC or message passing to implement inter-process communication. Furthermore, they must write code to handle partial failures such as slow responses or unavailable services. This is not especially difficult, but compared to language-level method or procedure calls in monolithic applications, this approach is more complex in a microservice environment.

Another microservice challenge arises from the partitioned database architecture. It is common in business transactions to update multiple entities simultaneously. This is straightforward in a monolithic application because there is only one database. In a microservice architecture, you need to update different databases used by different services. Using distributed transactions is not necessarily a good option, not only because of the CAP theorem, but also because today's highly scalable NoSQL databases and messaging middleware do not support them. Ultimately, you must use an eventual consistency approach, which places higher demands and challenges on developers.

Testing an application based on microservice architecture is also a complex task. For example, with the popular Spring Boot framework, testing the REST API of a monolithic web application is straightforward. Conversely, the same service test requires starting all related services (or at least their stubs). Again, the complexity introduced by adopting microservice architecture should not be underestimated.

Another challenge is that changes to a microservice architecture application can affect multiple services. For example, suppose you are implementing a feature that requires modifying services A, B, and C, where A depends on B and B depends on C. In a monolithic application, you simply change the relevant modules, integrate the changes, and deploy. By contrast, the microservice architecture pattern requires considering the impact of related changes on different services. For example, you need to update service C first, then B, and finally A. Fortunately, most changes typically affect only one service, and changes requiring multi-service coordination are rare.

Deploying a microservice application is also complex. A monolithic application only needs to be deployed on servers behind a load balancer. Each application instance requires configuration of infrastructure services such as databases and messaging middleware. By comparison, a microservice application typically consists of a large number of services. For example, according to Adrian Cockcroft, Hailo has 160 different services, and Netflix has approximately 600 services. Each service has multiple instances, resulting in many components that need to be configured, deployed, scaled, and monitored. Additionally, you need to implement a service discovery mechanism (to be covered in subsequent articles) for locating the addresses (including server addresses and ports) of communicating services. Traditional approaches cannot handle this level of complexity. Consequently, successfully deploying a microservice application requires developers to have sufficient control over deployment methods with a high degree of automation.

One approach to automation is using a PaaS such as Cloud Foundry. A PaaS provides developers with a simple way to deploy and manage microservices by packaging and solving all these problems internally. System and network specialists who configure the PaaS can apply best practices and policies to simplify these issues. Another approach to automating microservice deployment is to develop your own basic PaaS. A typical starting point is a clustering solution, such as using Mesos or Kubernetes with Docker. In later articles, we will examine how software-based deployment approaches such as NGINX can conveniently provide caching, access control, API statistics, and monitoring at the microservice level.

Summary

Building complex applications is genuinely difficult. Monolithic architecture is better suited for lightweight, simple applications. Using it for complex applications will lead to poor results. The microservice architecture pattern can be used to build complex applications, though this architectural model has its own drawbacks and challenges.

Screenshots on this page show the Chinese user interface. English materials are available on request.
E-mail: market@windica.cn  ·  We reply within 2 business days.