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
Database Performance Tuning: Master Indexing Strategies and Query Optimization Techniques
Learn how to minimize I/O latency and CPU cycles through effective indexing strategies like B-Tree and Hash indexes, covering indexes, and composite indexes. Master query optimization techniques including SARGable predicates, execution plan analysis, join optimization, and keyset pagination.
3 min readBuilding Resilient Distributed Systems: Circuit Breakers, Bulkheads, and Retry Patterns Explained
Master three essential patterns to prevent cascading failures and maintain system stability. Learn how to implement circuit breakers, bulkheads, and retry strategies with practical JavaScript examples.
5 min readRedis vs Memcached vs Hazelcast: The Ultimate Distributed Caching Guide
Compare Redis, Memcached, and Hazelcast architectures, features, and use cases to choose the right distributed caching solution for your application's performance and scalability needs.
4 min readMessage Queue Patterns: P2P, Pub/Sub, and Request-Reply Explained
Master asynchronous communication by comparing Point-to-Point, Publish-Subscribe, and Request-Reply patterns with practical code examples and reliability strategies.
3 min readWebSockets vs SSE vs WebRTC: Choosing the Right Real-Time Protocol
Compare WebSockets, Server-Sent Events, and WebRTC to choose the best protocol for your real-time application needs. Includes implementation examples, architecture comparisons, and security best practices.
5 min readContinue Reading
Database Performance Tuning: Master Indexing Strategies and Query Optimization Techniques
Learn how to minimize I/O latency and CPU cycles through effective indexing strategies like B-Tree and Hash indexes, covering indexes, and composite indexes. Master query optimization techniques including SARGable predicates, execution plan analysis, join optimization, and keyset pagination.
3 min readBuilding Resilient Distributed Systems: Circuit Breakers, Bulkheads, and Retry Patterns Explained
Master three essential patterns to prevent cascading failures and maintain system stability. Learn how to implement circuit breakers, bulkheads, and retry strategies with practical JavaScript examples.
5 min readRedis vs Memcached vs Hazelcast: The Ultimate Distributed Caching Guide
Compare Redis, Memcached, and Hazelcast architectures, features, and use cases to choose the right distributed caching solution for your application's performance and scalability needs.
4 min readReady to Supercharge Your Development Workflow?
Join thousands of engineering teams using MatterAI to accelerate code reviews, catch bugs earlier, and ship faster.
