Master Microservices Architecture: Service Boundaries, Data Ownership, and Communication Patterns
Microservices Architecture: Service Boundaries, Data Ownership, and Communication Patterns
Microservices architecture decomposes applications into independently deployable services around business capabilities. This guide covers the three critical design decisions that determine success: defining boundaries, managing data ownership, and selecting communication patterns.
Service Boundaries
Service boundaries define where one service ends and another begins. Poor boundaries create distributed monoliths with tight coupling and high latency.
Bounded Contexts
Use Domain-Driven Design (DDD) to identify bounded contexts. A bounded context encapsulates a specific domain model with its own ubiquitous language.
- Each microservice maps to one bounded context
- Aggregates within a bounded context are natural service candidates
- Domain services (stateless operations across aggregates) may become separate services
Conway's Law
Service boundaries must align with team structure. If two teams own different parts of the same service, you have a boundary problem.
- One team per service (Two Pizza Team rule)
- Independent deployment cycles
- Separate code repositories
Validation Criteria
Validate boundaries against these technical requirements:
- Single responsibility per service
- No chatty inter-service calls (high coupling indicator)
- Independent deployability
- No shared deployment dependencies
- Data consistency boundaries grouped together
Start coarse-grained. Splitting services is easier than refactoring across existing boundaries.
Data Ownership
Distributed data management is the hardest problem in microservices. Each service owns its data exclusively.
Database-per-Service Pattern
Each service has its own private database. Other services access data only through that service's API.
# Anti-pattern: Shared database
OrderService → [Shared Database] ← PaymentService
# Correct pattern: Database-per-service
OrderService → [Orders DB]
PaymentService → [Payments DB]
Benefits:
- Independent schema evolution
- Technology diversity (Postgres for orders, MongoDB for payments)
- Clear ownership and accountability
Trade-offs:
- No ACID transactions across services
- Data duplication is required
- Complex query patterns across services
Eventual Consistency
Accept that data will be temporarily inconsistent. Use the Saga Pattern to manage distributed transactions.
Saga Pattern: Choreography vs Orchestration
Choreography: Services emit events and react to others' events.
// Order Service emits event
await eventBus.publish('OrderCreated', {
orderId: '12345',
amount: 99.99,
items: [...]
});
// Payment Service listens and reacts
eventBus.subscribe('OrderCreated', async (event) => {
const payment = await processPayment(event);
await eventBus.publish('PaymentProcessed', { orderId: event.orderId, paymentId: payment.id });
});
Orchestration: A central coordinator manages the transaction flow.
// Order Orchestrator
async function createOrder(orderData) {
const order = await orderService.create(orderData);
try {
const payment = await paymentService.process(order.id, order.amount);
await inventoryService.reserve(order.items);
await notificationService.sendConfirmation(order.id);
} catch (error) {
await compensatingActions(order.id);
throw error;
}
}
Compensating transactions undo completed steps when a saga fails.
Data Retrieval Patterns
API Composition
Client calls multiple services and aggregates results.
async function getOrderDetails(orderId) {
const [order, customer, items] = await Promise.all([
orderService.get(orderId),
customerService.get(orderId),
itemService.getByOrder(orderId)
]);
return { order, customer, items };
}
Use when: Low latency requirements, simple joins, real-time data needed.
CQRS (Command Query Responsibility Segregation)
Separate read models from write models. Update read models asynchronously via events.
// Write side: Command
async function placeOrder(command) {
const order = await orderRepository.save(command);
await eventBus.publish('OrderPlaced', order);
}
// Read side: Query handler
eventBus.subscribe('OrderPlaced', async (event) => {
await readModel.upsert({
orderId: event.id,
customerName: event.customerName,
totalAmount: event.total,
status: 'PLACED'
});
});
// Query optimized for reads
async function getOrderSummary(orderId) {
return readModel.findOne({ orderId });
}
Use when: Complex read queries, high read-to-write ratio, multiple read views needed.
Communication Patterns
Synchronous Communication
REST/gRPC for request-response patterns.
// gRPC service definition
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
}
message CreateOrderRequest {
string customer_id = 1;
repeated OrderItem items = 2;
}
Use when:
- Immediate response required
- Simple request-response semantics
- Low-latency critical paths
Risks:
- Cascading failures
- Tight temporal coupling
- Blocking operations
Asynchronous Communication
Message brokers (RabbitMQ, Kafka) for event-driven architecture.
// Event schema with versioning
{
"eventType": "OrderShipped",
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-01-22T10:30:00Z",
"version": "2.0",
"data": {
"orderId": "12345",
"trackingNumber": "1Z999AA10123456784",
"carrier": "FedEx"
}
}
Critical requirements:
- Idempotency: Handle duplicate messages safely
- Ordering guarantees: Per-partition ordering in Kafka
- Dead letter queues: Handle failed messages
- Schema evolution: Backward-compatible event schemas
Resilience Patterns
Circuit Breaker
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = 0;
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
Service Discovery
Services locate each other dynamically without hardcoded URLs.
# Consul service registration
{
"ID": "order-service-1",
"Name": "order-service",
"Address": "10.0.1.5",
"Port": 8080,
"Check": {
"HTTP": "http://10.0.1.5:8080/health",
"Interval": "10s"
}
}
API Gateway
Single entry point that routes requests to appropriate services.
# Kong Gateway configuration
services:
- name: order-service
url: http://order-service:8080
routes:
- name: orders-route
service: order-service
paths:
- /api/v1/orders
Gateway responsibilities:
- Request routing
- Authentication and authorization
- Rate limiting
- Request/response transformation
- SSL termination
Getting Started
- Identify bounded contexts using domain modeling with business stakeholders
- Map aggregates to services - start with 5-10 services for most applications
- Implement database-per-service - no shared databases
- Choose communication pattern:
- Use synchronous for user-facing operations requiring immediate response
- Use asynchronous for background processing and cross-service consistency
- Implement resilience patterns - circuit breakers, retries, timeouts
- Set up observability - distributed tracing, metrics, logging
- Design for failure - assume network partitions and service unavailability
Share this Guide:
More Guides
Agentic Workflows: Building Self-Correcting Loops with LangGraph and CrewAI State Machines
Build production-ready AI agents that iteratively improve their outputs through automated feedback loops, combining LangGraph's state machine architecture with CrewAI's multi-agent orchestration for robust, self-correcting workflows.
14 min readBun Runtime Migration: Porting High-Traffic Node.js APIs with Native APIs and SQLite
Learn how to migrate high-traffic Node.js APIs to Bun for 4× HTTP throughput and 3.8× database performance gains using native APIs and bun:sqlite.
10 min readDeno 2.0 Workspaces: Build Monorepos with JSR Packages and TypeScript-First Development
Learn how to configure Deno 2.0 workspaces for monorepo management, publish TypeScript packages to JSR, and automate releases with OIDC-authenticated CI/CD pipelines.
7 min readGleam on BEAM: Building Type-Safe, Fault-Tolerant Distributed Systems
Learn how Gleam combines Hindley-Milner type inference with Erlang's actor-based concurrency model to build systems that are both compile-time safe and runtime fault-tolerant. Covers OTP integration, supervision trees, and seamless interoperability with the BEAM ecosystem.
5 min readHono Edge Framework: Build Ultra-Fast APIs for Cloudflare Workers and Bun
Master Hono's zero-dependency web framework to build low-latency edge APIs that deploy seamlessly across Cloudflare Workers, Bun, and other JavaScript runtimes. Learn routing, middleware, validation, and real-time streaming patterns optimized for edge computing.
6 min readContinue Reading
Agentic Workflows: Building Self-Correcting Loops with LangGraph and CrewAI State Machines
Build production-ready AI agents that iteratively improve their outputs through automated feedback loops, combining LangGraph's state machine architecture with CrewAI's multi-agent orchestration for robust, self-correcting workflows.
14 min readBun Runtime Migration: Porting High-Traffic Node.js APIs with Native APIs and SQLite
Learn how to migrate high-traffic Node.js APIs to Bun for 4× HTTP throughput and 3.8× database performance gains using native APIs and bun:sqlite.
10 min readDeno 2.0 Workspaces: Build Monorepos with JSR Packages and TypeScript-First Development
Learn how to configure Deno 2.0 workspaces for monorepo management, publish TypeScript packages to JSR, and automate releases with OIDC-authenticated CI/CD pipelines.
7 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
