How to define your application's microservice architecture — from understanding software architecture, through decomposition patterns, to defining service APIs.
Software architecture is the decomposition of a system into parts (elements) plus the relationships between those parts. This definition comes from Bass and colleagues. The way you decompose your system determines its quality attributes — the so-called "-ilities."
Quality Attributes (-ilities): Architecture determines maintainability, testability, deployability, scalability, and reliability. These properties matter far more than functional requirements when choosing an architecture.
An application's architecture can be described from multiple views. The 4+1 view model defines four views, each showing a different aspect:
| View | Focus | Example in Microservices |
|---|---|---|
| Logical | How developers organize the code | Hexagonal Architecture (Ports & Adapters) |
| Implementation | Build output — deployable units | Monolithic Architecture or Microservice Architecture |
| Process | Running components and their communication | Interprocess Communication (IPC) |
| Deployment | How processes map to machines | Infrastructure (containers, VMs, cloud) |
The "+1" is the set of scenarios (use cases) that drive the architecture. Each view answers a different question, and together they give a complete picture.
An architectural style, as defined by Garlan and Shaw, is a vocabulary of components and connectors plus a set of constraints on how they can be combined. Think of it as a template for organizing a system. The same application can use different styles for different views.
Analogy: An architectural style is like a building blueprint template. A "colonial style" house has specific rules about shape, roof, and columns. Similarly, a "hexagonal style" application has specific rules about how business logic connects to the outside world.
The traditional layered (three-tier) architecture organizes code into layers: presentation, business logic, and persistence. Each layer can only depend on the layer directly below it.
This style has several drawbacks:
These drawbacks motivate a better alternative: the Hexagonal Architecture.
In Hexagonal Architecture, the business logic sits at the center. It does not depend on anything external. Instead, it defines ports — interfaces for communicating with the outside world. Adapters connect external systems (web, database, messaging) to those ports.
Key Point: Adapters depend on the business logic, not the other way around. This keeps business logic independent and easy to test.
Note: Hexagonal Architecture is the recommended internal architecture for each individual microservice.
The implementation view decides how the application is packaged and deployed:
| Style | Structure | Deployment |
|---|---|---|
| Monolithic | Single deployable unit | One process or artifact |
| Microservice | Set of independently deployable services | Each service runs in its own process |
These two styles are alternatives. You choose one or the other for the implementation view.
The microservice architecture is a specific architectural style for the implementation view. It structures the application as a set of services (components) that communicate through protocols (connectors).
Each service internally uses Hexagonal Architecture for its logical view. So the implementation view (set of services) and the logical view (hexagonal design inside each service) work together.
A service is a standalone, independently deployable software component. It has:
Services interact only through their APIs. They never share databases or internal data structures. This loose coupling means you can change one service without changing others.
Warning: If two services share a database, they become tightly coupled. A change to the database schema can break both services. Always avoid shared databases.
Each service has its own private database (or schema). This gives two benefits:
It is fine to share libraries for stable, general-purpose utilities (e.g., logging, string manipulation). But business logic should never go into a shared library. If business logic changes, you would need to redeploy every service that uses the library. Put business logic in a service instead.
The "micro" in microservice is misleading. The size of a service (lines of code, number of endpoints) is an irrelevant metric. What matters is that a small team can own the service independently, with minimal coordination with other teams.
Analogy: Think of a service like a department in a company. A department's "size" does not matter — what matters is that it has clear responsibilities and can do its work without constant meetings with every other department.
Defining a microservice architecture is a creative, iterative process — not a mechanical procedure. It follows three main steps:
Note: These steps are not strictly sequential. You will go back and forth as you learn more about the domain.
The first step creates two outputs from the application requirements:
Each system operation has:
System operations are categorized as commands (create, update, delete data) or queries (read data).
A business capability is something a business does to generate value. It describes what the business does, not how it does it. Business capabilities are identified by analyzing the organization's purpose and structure.
Key Point: Services should be organized around business concerns, not technical concerns. Do not create a "database service" or a "logging service." Instead, create services like "Order Service" or "Restaurant Service."
Domain-Driven Design (DDD) offers an alternative decomposition strategy. DDD says that every complex business has multiple subdomains, and each subdomain has its own domain model.
Both decomposition patterns — by Business Capability and by Subdomain — produce services organized around business concerns. In practice, they often give very similar results.
Without DDD, organizations often try to build a single enterprise-wide data model. This approach fails because:
DDD solves this by using multiple domain models, each within its own Bounded Context. The same real-world entity has different representations in different services:
| Service | Entity Name | Focus |
|---|---|---|
| Order Service | Order | Consumer details, payment, line items |
| Kitchen Service | Ticket | Items to prepare, preparation status |
| Delivery Service | Delivery | Pickup and drop-off addresses, courier |
Consistency across these different representations is maintained through Sagas and domain events. The API Gateway translates between domain models when a user interface needs data from multiple services.
Note: Event Storming is an alternative, collaborative technique for discovering domain models and service boundaries. Teams place domain events on a timeline and group them into aggregates and bounded contexts.
Two object-oriented design principles guide good decomposition:
Warning: If every business change requires coordinated deployment of multiple services, you have a distributed monolith — not a microservice architecture. Apply SRP and CCP to avoid this.
Decomposition is not always straightforward. Several obstacles can make it harder or force compromises.
When services communicate over the network, each call adds latency. If a particular decomposition leads to too many round-trips between services, the system becomes slow. Solutions include:
If Service A calls Service B synchronously, and Service B is down, then Service A is also effectively down. This reduces overall availability. Solutions include:
In a monolith, you can use a single database transaction to keep data consistent. With microservices, each service has its own database. You cannot use traditional two-phase commit (2PC) across services.
The solution is the Saga pattern: a sequence of local transactions coordinated by messages. Sagas provide eventual consistency instead of immediate consistency. Data that must be atomically consistent must live in the same service.
Because each service has its own database, you cannot get a globally consistent snapshot of all data at one point in time. In practice, this is rarely a real problem.
A God Class is a large class used throughout the application — like an "Order" class that every part of the system depends on. If you keep a single Order class, it couples all services together.
DDD solves this: each service has its own version of the entity, with only the attributes it needs. The Order Service has an Order, the Kitchen Service has a Ticket, and the Delivery Service has a Delivery.
Key Point: God Classes are solved by DDD's concept of multiple domain models. Each Bounded Context defines its own representation of shared business concepts.
| Obstacle | Problem | Solution |
|---|---|---|
| Network Latency | Too many remote calls | Batch APIs or merge services |
| Synchronous IPC | Cascading failures | Async messaging or CQRS replicas |
| Data Consistency | No distributed transactions | Sagas (eventual consistency) |
| Consistent View | No global snapshot | Accept eventual consistency; rarely a problem |
| God Classes | Tight coupling via shared entities | DDD: each service has its own entity version |
After decomposing into services, you need to define each service's API. This is done by assigning system operations to services.
Each system operation is assigned to the service that owns the data (information) the operation needs. This service becomes the entry point for that operation.
Some operations need data or actions from multiple services. For example, createOrder starts in the Order Service but must also verify the consumer (Consumer Service), check the restaurant menu (Restaurant Service), create a kitchen ticket (Kitchen Service), and authorize payment (Accounting Service).
This collaboration means that each participating service needs additional API operations that support the cross-service workflow.
Note: The choice of IPC mechanism (REST, gRPC, messaging) is covered in Chapter 3. The coordination pattern for multi-service operations (Sagas) is covered in Chapter 4.