
When developing an enterprise-class client-server application, it is crucial to consider support for various clients such as desktop, mobile browsers, and native mobile applications. The application must expose an API for potential consumption by other third parties, and it must also integrate with other applications via web services or message brokers. The application handles requests (HTTP requests and messages) by executing business logic, accessing the database, exchanging messages with other systems, and returning HTML/JSON/XML responses. All previously mentioned components must be considered when implementing the different functional domains of the application.
Problem What is the deployment mode of the application?
Forces
- There is a team of developers working on the application.
- New team members need to become productive quickly.
- The codebase must be easy to understand and modify.
- Strong practice of continuous deployment of the application.
- Multiple copies of the application need to be run on several machines to meet scalability and availability requirements.
- You want to take advantage of emerging technologies (frameworks, programming languages, types of data sources, etc.).
Solution Define an architecture that structures the application as a group of associated and loosely coupled services. Each service implements a set of highly cohesive functions. For example, an application may consist of multiple microservices such as the order management service, the customer management service, etc.
Services communicate using synchronous protocols such as HTTP/REST or asynchronous protocols such as AMQP (Advanced Message Queuing Protocol). Services can be developed and deployed independently of each other. Each service has its own database to decouple it from other services. Data consistency between services is maintained using an event-driven architecture (EDA).
Expected Benefits This solution offers several advantages:
- Each microservice is relatively small and easier to understand for a developer.
- The IDE is faster, making developers more productive.
- The learning curve for teams is reduced.
- The application starts up faster, making developers more productive and speeding up deployments.
- Each service can be deployed independently of other services.
- Development is easier to scale by allowing you to organize development efforts around multiple teams.
- Each team owns and is responsible for one or more individual services.
- Each team can develop, deploy, and extend its services independently of all other teams.
- Improved fault isolation; if there is a memory leak in one service, only that service will be affected, while other services will continue to process requests.
- Eliminates long-term commitment to a technology stack. When developing a new service, you can choose a new technology stack. Similarly, when making major changes to an existing service, you can rewrite it using a new technology stack.
Drawbacks This solution also has several drawbacks:
- Developers must deal with the additional complexity of building a distributed system.
- Development tools/IDEs are oriented towards building monolithic applications and do not provide explicit support for developing distributed applications.
- Testing becomes more difficult.
- Developers need to set up inter-service communication mechanisms.
- Implementing use cases that cover multiple services requires careful coordination between teams.
- Deployment complexity. In production, there is also operational complexity in deploying and managing a system composed of many different types of services.
How to Decompose a Service? Another challenge is deciding how to partition the system into microservices. This is more art than technique, but there are several strategies that can help:
- Decompose by business capability and define services corresponding to business capabilities.
- Decompose by domain-driven design and define services that align with domain boundaries.
- Decompose by verb or use case and define services responsible for specific actions. For example, a shipping service responsible for shipping complete orders.
- Decompose by nouns or resources by defining a service that is responsible for all operations on entities/resources of a given type. For example, an account service responsible for managing user accounts. Ideally, each service should have only a small set of responsibilities.
How to Maintain Data Consistency? To ensure loose coupling between services, each service has its own database. Maintaining data consistency between services is a challenge because two-dimensional/distributed transactions are not an option for many applications. Instead, an application should use an event-driven architecture. A service publishes an event when its data changes. Other services consume this event and update their data. There are several ways to reliably update data.