🏢 Microservices Enterprise Patterns
1. 🎯 Service Decomposition Patterns
1.1 Strangler Fig Pattern
Concept: Gradually migrate a monolithic application to microservices by intercepting and redirecting requests.
When to Use:
- Legacy system modernization
- Risk-averse transitions
- Phased migrations
Implementation Strategy:
@Configuration
public class StranglerConfig {
@Bean
public RouteLocator stranglerRoutes(RouteLocatorBuilder builder) {
return builder.routes()
// New microservice handling orders
.route("new-orders", r -> r
.path("/api/v2/orders/**")
.uri("lb://order-service"))
// Legacy system fallback
.route("legacy-orders", r -> r
.path("/api/orders/**")
.uri("http://legacy-system/orders"))
.build();
}
}
1.2 Domain-Driven Decomposition
Concept: Break down services based on business domain boundaries.
Key Principles:
- Bounded Contexts
- Ubiquitous Language
- Context Mapping
Example:
// Order Bounded Context
@DomainService
public class OrderService {
@Aggregate
public class Order {
private OrderId id;
private CustomerId customerId;
private Money totalAmount;
private OrderStatus status;
public void process() {
validateOrder();
calculateTotal();
updateStatus(OrderStatus.PROCESSING);
publishEvent(new OrderProcessedEvent(this));
}
}
}
// Shipping Bounded Context
@DomainService
public class ShippingService {
@Aggregate
public class Shipment {
private ShipmentId id;
private OrderId orderId;
private Address destination;
private ShipmentStatus status;
public void dispatch() {
validateAddress();
assignCarrier();
updateStatus(ShipmentStatus.DISPATCHED);
publishEvent(new ShipmentDispatchedEvent(this));
}
}
}
2. 🔄 Integration Patterns
2.1 Saga Pattern
Concept: Manage distributed transactions across multiple services.
Types:
- Choreography-based Saga
- Orchestration-based Saga
Choreography Example:
@Service
public class OrderSaga {
@Transactional
public void createOrder(OrderCreateCommand cmd) {
// Local transaction
Order order = orderRepository.save(new Order(cmd));
// Publish event for payment service
eventPublisher.publish(new OrderCreatedEvent(order));
}
@EventHandler
public void onPaymentCompleted(PaymentCompletedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.markAsPaid();
// Publish event for inventory service
eventPublisher.publish(new OrderPaidEvent(order));
}
@EventHandler
public void onInventoryReserved(InventoryReservedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.markAsReady();
// Publish event for shipping service
eventPublisher.publish(new OrderReadyForShipmentEvent(order));
}
}
Orchestration Example:
@Service
public class OrderOrchestrator {
private final OrderService orderService;
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final ShippingService shippingService;
@Transactional
public OrderResult processOrder(OrderCommand cmd) {
try {
// Create order
Order order = orderService.createOrder(cmd);
// Process payment
PaymentResult payment = paymentService.processPayment(
new PaymentCommand(order));
if (!payment.isSuccessful()) {
return compensateOrder(order);
}
// Reserve inventory
InventoryResult inventory = inventoryService.reserve(
new InventoryCommand(order));
if (!inventory.isSuccessful()) {
paymentService.refund(payment);
return compensateOrder(order);
}
// Create shipment
ShipmentResult shipment = shippingService.createShipment(
new ShipmentCommand(order));
return new OrderResult(order, payment, inventory, shipment);
} catch (Exception e) {
return handleError(e);
}
}
}
2.2 API Composition Pattern
Concept: Aggregate data from multiple services to fulfill a client request.
Implementation:
@Service
public class OrderDetailsCompositionService {
private final OrderService orderService;
private final CustomerService customerService;
private final PaymentService paymentService;
private final ShippingService shippingService;
public OrderDetailsDTO getOrderDetails(String orderId) {
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(() ->
orderService.getOrder(orderId));
CompletableFuture<CustomerInfo> customerFuture =
orderFuture.thenCompose(order ->
CompletableFuture.supplyAsync(() ->
customerService.getCustomer(order.getCustomerId())));
CompletableFuture<PaymentInfo> paymentFuture =
CompletableFuture.supplyAsync(() ->
paymentService.getPaymentInfo(orderId));
CompletableFuture<ShipmentInfo> shipmentFuture =
CompletableFuture.supplyAsync(() ->
shippingService.getShipmentInfo(orderId));
return CompletableFuture.allOf(
orderFuture, customerFuture, paymentFuture, shipmentFuture)
.thenApply(v -> new OrderDetailsDTO(
orderFuture.join(),
customerFuture.join(),
paymentFuture.join(),
shipmentFuture.join()))
.join();
}
}
3. 🛡️ Reliability Patterns
3.1 Bulkhead Pattern
Concept: Isolate service dependencies to prevent cascading failures.
Implementation:
@Configuration
public class BulkheadConfig {
@Bean
public ThreadPoolBulkhead orderServiceBulkhead() {
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(5)
.queueCapacity(100)
.build();
return ThreadPoolBulkhead.of("orderService", config);
}
@Bean
public ThreadPoolBulkhead paymentServiceBulkhead() {
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(5)
.coreThreadPoolSize(3)
.queueCapacity(50)
.build();
return ThreadPoolBulkhead.of("paymentService", config);
}
}
@Service
public class OrderService {
@Bulkhead(name = "orderService", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<Order> processOrder(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> {
// Process order
return orderProcessor.process(request);
});
}
}
3.2 Rate Limiter Pattern
Concept: Control the rate of requests to protect services from overload.
Implementation:
@Configuration
public class RateLimitConfig {
@Bean
public RateLimiter orderRateLimiter() {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMillis(500))
.build();
return RateLimiter.of("orderService", config);
}
}
@RestController
public class OrderController {
@RateLimiter(name = "orderService")
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(
@RequestBody OrderRequest request) {
return ResponseEntity.ok(orderService.createOrder(request));
}
}
4. 📡 Communication Patterns
4.1 Event Sourcing Pattern
Concept: Store state changes as a sequence of events.
Implementation:
@Aggregate
public class OrderAggregate {
private OrderState state;
private List<OrderEvent> changes = new ArrayList<>();
public void process(CreateOrderCommand cmd) {
// Business logic validation
validateOrder(cmd);
// Apply event
OrderCreatedEvent event = new OrderCreatedEvent(
cmd.getOrderId(),
cmd.getCustomerId(),
cmd.getItems()
);
apply(event);
changes.add(event);
}
public void markAsPaid(ProcessPaymentCommand cmd) {
validatePayment(cmd);
OrderPaidEvent event = new OrderPaidEvent(
cmd.getOrderId(),
cmd.getPaymentId(),
cmd.getAmount()
);
apply(event);
changes.add(event);
}
private void apply(OrderCreatedEvent event) {
this.state = new OrderState(event.getOrderId());
this.state.setStatus(OrderStatus.CREATED);
}
private void apply(OrderPaidEvent event) {
this.state.setStatus(OrderStatus.PAID);
this.state.setPaymentId(event.getPaymentId());
}
public List<OrderEvent> getUncommittedChanges() {
return new ArrayList<>(changes);
}
public void markChangesAsCommitted() {
changes.clear();
}
}
4.2 CQRS Pattern
Concept: Separate read and write operations for better scalability.
Implementation:
// Write Model
@Service
public class OrderCommandService {
private final EventStore eventStore;
@Transactional
public void createOrder(CreateOrderCommand cmd) {
OrderAggregate aggregate = new OrderAggregate();
aggregate.process(cmd);
eventStore.saveEvents(cmd.getOrderId(),
aggregate.getUncommittedChanges());
}
}
// Read Model
@Service
public class OrderQueryService {
private final OrderReadRepository repository;
public OrderDTO getOrder(String orderId) {
return repository.findById(orderId)
.map(this::toDTO)
.orElseThrow(() ->
new OrderNotFoundException(orderId));
}
@EventHandler
public void on(OrderCreatedEvent event) {
OrderReadModel order = new OrderReadModel();
order.setId(event.getOrderId());
order.setStatus(OrderStatus.CREATED);
repository.save(order);
}
@EventHandler
public void on(OrderPaidEvent event) {
OrderReadModel order = repository.findById(event.getOrderId())
.orElseThrow();
order.setStatus(OrderStatus.PAID);
order.setPaymentId(event.getPaymentId());
repository.save(order);
}
}
5. 📊 Monitoring and Observability
5.1 Log Aggregation Pattern
Implementation:
@Aspect
@Component
public class LoggingAspect {
private static final String CORRELATION_ID = "correlationId";
@Around("@annotation(LogOperation)")
public Object logOperation(ProceedingJoinPoint joinPoint)
throws Throwable {
String correlationId = MDC.get(CORRELATION_ID);
if (correlationId == null) {
correlationId = generateCorrelationId();
MDC.put(CORRELATION_ID, correlationId);
}
try {
log.info("Starting operation: {}",
joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
log.info("Completed operation: {}",
joinPoint.getSignature().getName());
return result;
} catch (Exception e) {
log.error("Operation failed: {}",
joinPoint.getSignature().getName(), e);
throw e;
} finally {
MDC.remove(CORRELATION_ID);
}
}
}
6. 🔍 Best Practices
-
Service Independence
- Avoid shared databases
- Use asynchronous communication
- Implement proper service boundaries
-
Data Consistency
- Use eventual consistency where possible
- Implement compensation logic
- Handle partial failures gracefully
-
Security
- Implement authentication at the gateway
- Use service-to-service authentication
- Encrypt sensitive data
-
Performance
- Use caching strategically
- Implement proper timeout handling
- Monitor service metrics
7. 📚 References
- "Microservices Patterns" by Chris Richardson
- "Building Microservices" by Sam Newman
- "Domain-Driven Design" by Eric Evans
- "Enterprise Integration Patterns" by Gregor Hohpe