System Design: Microservices Architecture

James Han
6 min readDec 11, 2022

The microservices architecture is a relatively new architecture style that has become very popular in recent years. This architecture style is inspired by the concept of domain-driven design (DDD), where code is organized in terms of the business domain or function that they serve, and code is highly decoupled across different domains.

Architecture Components

In this section, we discuss a few major components in the microservices architecture.

Services

The microservices architecture consists of multiple independent, decoupled, and single-purpose services. All of the dependent components, including databases, are packaged into each service. Services communicate with each other through APIs, and it’s common for systems to have a shared API layer that unifies cross-service communications.

API Layer

The API layer sits between the consumers of the system (user interfaces or calls from other systems) and the services. When users make requests on the user interface, these requests are routed to the appropriate services through the API layer.

Frontend

There are two types of frontends in the microservices architecture. The first type is the monolithic frontend, where all of the services share a single frontend. This frontend could be a web, mobile, or desktop application. Certain business needs and practical constraints make this approach necessary for some applications, such as the desire to make the user interface more coherent when the relationship between different components is complicated.

The second type is microfrontends. This approach achieves the same level of granularity for the frontend as the backend by isolating user interfaces in their own components. In this approach, teams can develop their UI components in conjuncture with the backend service, and the scope is entirely isolated within the service boundary. One common way to implement this approach is through component-based web frameworks such as React and Vue.

Key Properties

In this section, we discuss a few key properties of the microservices architecture that set them apart from other architecture styles.

Distributed

The distributed nature of the microservices architecture means that all services are decoupled and run independently of each other. Each service runs in its own process, and does not share network bandwidth, memory, and disk space with any other services. This allows each service to scale independently, and avoids resource contention between services. The downside is that communication between services is negatively affected, because communication must take place through network calls, which are slower than method calls. Authentication and authorization between each service also adds complexity and processing time. Because of these downsides, it is critically important to choose the right level of granularity of services in a microservices architecture.

Granularity

When deciding the granularity of services, a few guidelines should be followed:

  1. Purpose-Driven: Services should be divided based on their purpose, or the domain that they serve. Ideally, each service should be functionally cohesive and serve a single significant purpose to the entire application.
  2. Isolated Transaction: Each service should contain all of the subcomponents needed to complete a single transaction.
  3. Limited Communication: Services may communicate with each other, but it’s best to keep this communication at a minimum, because it adds overhead and hinders the performance of the application.

Bounded Context and Data Isolation

In the microservices architecture, every service is self-contained and models a domain or workflow in the entire system. This is known as domain-partitioned architecture, which exemplifies the philosophy of domain-driven design.

Each service contains all of the classes, subcomponents, and database schemas that the service needs, and does not share any common code with other services. One downside is code duplication. Another downside is data consistency. Since every service contains its own database schemas, when the same data needs to be shared between services, the data consistency problem surfaces. A single service should be chosen to hold the single source of truth, and other services should communicate with this service if any data is needed. The alternative is to duplicate the data across different services, but maintaining data consistency will be tricky. This is not all bad, however, because every service now has the freedom to choose their own data storage solution.

Operational Reuse

Even though services are decoupled and duplication is done as a design choice, there are certain operational components that should not be duplicated, such as monitoring and logging. This is because every service will benefit from these capabilities, and it becomes a maintenance nightmare if every team is responsible for handling these capabilities for their services.

In this situation, the sidecar component is introduced. The sidecar component is usually owned by a shared infrastructure team, and it handles all of the operational concerns of the services. This component sits inside every service, and they are connected through a common service plane that controls these sidecars globally. Whenever it is upgraded, each service will receive the upgraded version through the service plane.

Communication

One of the key decisions in designing a microservices architecture is in deciding the granularity of services, and this decision is largely driven by the communication between different services. Services should remain decoupled, yet still coordinated in useful ways and have efficient communication. The fundamental decision to make is whether the communication is synchronous or asynchronous.

Synchronous Communication

In synchronous communication, the caller waits for a response from the callee. In a microservices architecture, synchronous communication typically enforces protocol-aware heterogeneous interoperability. This term is a little complicated, so here is a breakdown:

  • Protocol-aware: each service knows how to call other services. This is achieved by standardizing the communication protocol such as REST.
  • Heterogeneous: each service is able to be written in a different technology stack.
  • Interoperability: services are able to communicate with other services through network calls.

Asynchronous Communication

In asynchronous communication, the caller does not wait for a response from the callee. In a microservices architecture, asynchronous communication is usually achieved using events and messages. This means that a microservices architecture may internally use an event-driven architecture, using choreography and orchestration patterns that are similar to the broker and mediator patterns.

Choreography is a pattern where each service calls other services as needed, and there is no central coordinator. This pattern respects the bounded context philosophy and preserves the decoupling of services. However, the downside is that error handling is more difficult, and coordinating communication across multiple services becomes very complex. This is because each service performs a specialized task with little knowledge of the other services, so if a complicated transaction needs to take place, each service only knows the state of a small piece of the whole picture, so more complicated communication needs to take place to complete the transaction.

Orchestration is a pattern that introduces a specialized service whose only responsibility is to coordinate communication between services. Instead of services directly communicating with each other, every request is sent to and received from this orchestrator. This pattern optimizes for complex business processes by coordinating the complex communication between services. However, it inherently couples services, so it should be used carefully and with domain-specific scope.

Trade-Offs

Advantages

  • The microservices architecture supports automated testing and deployment very easily. Every service can be tested and deployed independently.
  • This architecture style is highly scalable, highly elastic, and highly evolutionary. This is because every service can be developed and scaled independently. This style works very well with modern development practices such as Agile, because it allows each service to be incrementally improved and their changes easily deployed.

Disadvantages

  • The microservices architecture faces reliability and fault tolerance problems when there is too much communication between the services. These problems can be alleviated using redundancy of services.
  • Performance is often an issue in microservices because network calls are slower than function calls within a monolith. Authentication and authorization also add overhead to communication between services.

--

--