Your microservice owns its data. That’s the core idea, but it’s less about possession and more about a strict boundary that prevents other services from touching it directly.
Let’s see this in action. Imagine an OrderService that manages order data, and a ProductService that manages product data.
// OrderService's data store (e.g., PostgreSQL table 'orders')
{
"order_id": "ORD12345",
"customer_id": "CUST987",
"items": [
{"product_id": "PROD001", "quantity": 2},
{"product_id": "PROD005", "quantity": 1}
],
"status": "PENDING"
}
// ProductService's data store (e.g., PostgreSQL table 'products')
{
"product_id": "PROD001",
"name": "Wireless Mouse",
"price": 25.99,
"stock": 150
}
If the OrderService needs to display the name and price of products in an order, it doesn’t query the ProductService’s database directly. Instead, it relies on an API provided by the ProductService or, more commonly, it replicates the necessary product data within its own bounded context.
Here’s how the OrderService might store a denormalized view of product data for its own use:
// OrderService's data store (denormalized product data within the order record)
{
"order_id": "ORD12345",
"customer_id": "CUST987",
"items": [
{"product_id": "PROD001", "quantity": 2, "product_name": "Wireless Mouse", "unit_price": 25.99},
{"product_id": "PROD005", "quantity": 1, "product_name": "Mechanical Keyboard", "unit_price": 79.99}
],
"status": "PENDING"
}
This pattern addresses the "bounded context" problem from Domain-Driven Design. Each microservice is designed around a specific business capability (a "bounded context"), and its data model reflects that capability exclusively. No other service can reach into its database and start making assumptions or direct modifications. This isolation is critical for independent development, deployment, and scaling. When you change the ProductService’s internal schema, the OrderService might not even notice, as long as the API contract or the replicated data it depends on remains consistent.
The primary problem this solves is the "distributed monolith" anti-pattern, where services are technically separate but so tightly coupled through shared databases or direct data access that they can’t be managed independently. By enforcing data ownership, you decouple services at the data level. The OrderService can evolve its order-specific logic and even its internal representation of product data without breaking the ProductService, and vice-versa. This allows teams to work autonomously, reducing coordination overhead and accelerating delivery.
Internally, this means each service has its own database instance, managed by that service’s team. When data needs to be shared or correlated, it’s done through well-defined APIs or asynchronous event mechanisms. For example, when a product price changes in the ProductService, it might publish a ProductPriceUpdated event. The OrderService would subscribe to this event and update its denormalized product data accordingly. This event-driven approach ensures eventual consistency.
The exact levers you control are the API contracts between services and the choice of data replication or event-driven synchronization strategies. You’re deciding what data is essential for a service to fulfill its responsibilities and how to keep that data reasonably up-to-date without creating direct dependencies. This often involves making trade-offs between data freshness and service independence.
Most people think of "owning your data" as a simple database-per-service rule. The subtler, more powerful aspect is that this ownership extends to the schema and the semantics of that data within the bounded context. The OrderService doesn’t just "own" its orders table; it owns the concept of an order, including any product details it needs to display or process that order, and it will model those details in a way that serves its purpose, even if that means duplicating and slightly transforming data from another service.
This leads to the next challenge: managing data consistency across these independently owned datasets.