The initial, most surprising truth about splitting a monolith by domain is that the "domain" isn’t a static, well-defined business boundary; it’s a fluid concept that evolves alongside your understanding of the system and business needs.

Imagine a monolithic e-commerce application. It handles everything: user accounts, product catalog, order processing, payments, shipping, and inventory. If we decide to split this by domain, we’d first identify these core business capabilities.

Here’s a simplified view of what that might look like in practice, showing how different parts of the monolith might be carved out.

Monolith (Conceptual):

+-----------------------+
|                       |
|      Monolith         |
|                       |
| +-------------------+ |
| |   User Service    | |
| | (Auth, Profile)   | |
| +-------------------+ |
|                       |
| +-------------------+ |
| |  Product Catalog  | |
| | (Details, Search) | |
| +-------------------+ |
|                       |
| +-------------------+ |
| |  Order Processing | |
| | (Cart, Checkout)  | |
| +-------------------+ |
|                       |
| +-------------------+ |
| |     Payment       | |
| |   (Gateway Int.)  | |
| +-------------------+ |
|                       |
| +-------------------+ |
| |     Shipping      | |
| | (Fulfillment)     | |
| +-------------------+ |
|                       |
| +-------------------+ |
| |    Inventory      | |
| | (Stock Levels)    | |
| +-------------------+ |
|                       |
+-----------------------+

Now, let’s decompose this into microservices based on these domains.

Decomposed Microservices:

  • User Service: Handles user registration, login, profile management, and authentication.
  • Product Service: Manages product information, categories, attributes, and search indexing.
  • Order Service: Deals with shopping carts, order creation, order status updates, and checkout logic.
  • Payment Service: Integrates with payment gateways, processes transactions, and handles payment status.
  • Shipping Service: Manages shipping addresses, fulfillment requests, and tracking information.
  • Inventory Service: Tracks stock levels for all products.

The key here is that each service owns its data and logic. The Order Service doesn’t directly query the Inventory database; it would call the Inventory Service’s API.

Example: User Registration Flow (Conceptual)

  1. User submits registration form via UI.
  2. API Gateway routes request to User Service.
  3. User Service validates input, creates a new user record in its users table, and publishes a UserRegistered event.
  4. Product Service might subscribe to UserRegistered to potentially pre-populate a user’s wish list or preferences if that’s a feature.
  5. Order Service might subscribe to UserRegistered to initialize an empty shopping cart for the new user.

This separation allows teams to work independently on their respective services, deploy them independently, and scale them independently.

The mental model for decomposition centers on identifying bounded contexts, a concept from Domain-Driven Design (DDD). A bounded context is a linguistic and conceptual boundary within which a particular domain model is defined and applicable. It’s where a specific term, like "Product," has a precise meaning. In a monolith, these boundaries are often blurred. In microservices, we enforce them.

When decomposing, you’re essentially drawing these boundaries and creating independent deployable units that communicate over well-defined interfaces (APIs, message queues).

The levers you control are:

  • Service Granularity: How small or large should each service be? Too small leads to micro-service hell; too large, and you’re still stuck with a distributed monolith.
  • Communication Patterns: Synchronous (REST, gRPC) vs. asynchronous (message queues like Kafka, RabbitMQ). This choice profoundly impacts resilience and coupling.
  • Data Ownership: Each service should ideally own its data. This often means introducing new databases per service or carefully partitioning existing ones.
  • Team Structure: Conway’s Law dictates that your system architecture will mirror your communication structure. Organize teams around domains.

One common pitfall is to decompose based on technical layers (e.g., a "UI service," a "business logic service," a "data access service"). This creates a distributed monolith where changes in one technical layer require coordinated deployments across multiple services, defeating the purpose. Domain-based decomposition ensures that a single service can often handle a full business capability end-to-end, minimizing cross-service dependencies for core features.

The next logical step after decomposing by domain is understanding how to manage distributed transactions and eventual consistency across these new service boundaries.

Want structured learning?

Take the full Microservices course →