Macroservices

A Macroservice, also known as a Domain Monolith, is an architectural pattern that groups services related to a single, cohesive business domain into a single deployable unit. It represents a middle ground between a large, undifferentiated monolith and a granular, highly-distributed microservices architecture.

The goal is to retain the development velocity and clear ownership boundaries of a monolith within a specific domain, while enforcing strict, loosely-coupled API and event-driven communication between different domains.

The Architectural Pendulum: A Recent History

To understand the value of Macroservices, it's useful to understand the path many engineering organizations have taken.

1. The Pain of the Monolith

We initially moved away from monoliths for valid reasons:

2. The Promise and Peril of Microservices

Microservices promised to solve this. Each service was small, nimble, and easy to understand within its boundary. However, this architectural freedom came at a high operational cost. The complexity didn't disappear; it moved from the code into the network.

This often leads to the Distributed Monolith, an anti-pattern with the downsides of both architectures:

We really worked hard to try to avoid monoliths but we’ve ended up creating a lot of work for ourselves! Not much of it feels productive though. Most of the above act like a drag on what felt productive at the beginning.

The Synthesis: Macroservices as Domain Boundaries

The Macroservice pattern provides a pragmatic solution by aligning the service architecture with the business architecture. It draws heavily on principles from Domain-Driven Design (DDD), especially the concept of a Bounded Context.

A Bounded Context is the logical boundary within which a specific domain model is defined and applicable. Inside the context, terms are unambiguous. (e.g., inside the "Billing" context, a "Customer" has a payment method; inside the "Support" context, a "Customer" has a history of tickets).

A Macroservice is the technical implementation of a single, well-defined Bounded Context.

Guiding Principles

  1. High Internal Cohesion: Services and logic within the macroservice are highly related and can be tightly coupled. They can share a database, call each other directly, and be deployed as a single unit. This eliminates vast amounts of internal network and contract overhead.
  2. Low External Coupling: The macroservice exposes a limited, well-defined, and stable API to the outside world. This is its public contract. Communication between macroservices should be asynchronous and event-driven wherever possible.
  3. Clear Team Ownership: A single team owns the macroservice. They are responsible for its internal architecture, implementation, and its public contract. This aligns with Conway's Law.

How to Define a Macroservice Boundary

Identifying the "seams" for your macroservices is the most critical part of this design.

  1. Identify Business Capabilities: Look at your organization. What are the core functions it performs? Billing, Identity, Inventory Management, Search, Content Ingestion. These are strong candidates.
  2. Model with Events: Think about the "facts" or events that occur in your business. UserSignedUp, PaymentProcessed, ArticlePublished. As described in Building Event-Driven Microservices - Adam Bellemare, these events form the basis of a durable, reusable data communication layer. The services that produce or are the primary authority on these events often belong in the same macroservice.
  3. Establish the Public Contract: The primary interface between macroservices should be an Event Stream.
    • Service A doesn't call Service B's REST endpoint to get data.
    • Instead, Service B subscribes to the UserSignedUp event stream produced by Service A and materializes its own view of the data it needs.
    • This decouples the services in time and availability. Service A doesn't need to know Service B exists, and a failure in Service B will not cascade to Service A.

Example Workflow

In this model, the internal complexity of each domain is contained within its respective monolith (the macroservice), while the communication between them is resilient, scalable, and decoupled.


Further Reading & Resources