Implementation Example: Orchestration-based Saga
public class OrderSagaOrchestrator {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final ShippingService shippingService;
public void startOrderSaga(String orderId) {
Order order = orderRepository.findById(orderId);
try {
// Step 1: Process payment
PaymentResult paymentResult = paymentService.processPayment(
order.getCustomerId(),
order.getTotalAmount()
);
order.setPaymentId(paymentResult.getPaymentId());
orderRepository.save(order);
// Step 2: Reserve inventory
try {
inventoryService.reserveInventory(order.getItems());
} catch (Exception e) {
// Compensate payment
paymentService.refundPayment(paymentResult.getPaymentId());
throw e;
}
// Step 3: Schedule shipping
try {
ShippingResult shippingResult = shippingService.scheduleShipping(
order.getCustomerId(),
order.getShippingAddress(),
order.getItems()
);
order.setShippingId(shippingResult.getShippingId());
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order);
} catch (Exception e) {
// Compensate inventory
inventoryService.releaseInventory(order.getItems());
// Compensate payment
paymentService.refundPayment(paymentResult.getPaymentId());
throw e;
}
} catch (Exception e) {
order.setStatus(OrderStatus.FAILED);
order.setFailureReason(e.getMessage());
orderRepository.save(order);
}
}
}
When to Use
The Saga pattern is useful when:
- You need to maintain data consistency across multiple services
- Traditional distributed transactions (2PC) are not feasible
- You’re working with a microservices architecture
Challenges
- Designing compensation logic for each step
- Handling partial failures
- Managing saga state and recovery
- Dealing with concurrency issues
5. Event-Carried State Transfer
This pattern uses events to propagate state changes to other services, reducing the need for synchronous API calls.
Implementation Example
// Product Service
public class ProductService {
public void updateProduct(UpdateProductRequest request) {
// Update product in database
Product product = productRepository.findById(request.getProductId());
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setPrice(request.getPrice());
product.setInventoryCount(request.getInventoryCount());
productRepository.save(product);
// Publish event with complete product state
eventBus.publish(new ProductUpdatedEvent(
product.getId(),
product.getName(),
product.getDescription(),
product.getPrice(),
product.getInventoryCount(),
product.getCategories()
));
}
}
// Search Service
public class SearchService {
@EventHandler
public void on(ProductUpdatedEvent event) {
// Update search index with product data from event
SearchDocument document = new SearchDocument();
document.setId(event.getProductId());
document.setTitle(event.getName());
document.setDescription(event.getDescription());
document.setPrice(event.getPrice());
document.setAvailable(event.getInventoryCount() > 0);
document.setCategories(event.getCategories());
searchRepository.save(document);
}
}
When to Use
Event-carried state transfer is valuable when:
- Services need to maintain their own copy of data owned by another service
- You want to reduce synchronous API calls between services
- You need to update multiple read models when data changes
Challenges
- Managing large event payloads
- Handling schema evolution
- Ensuring eventual consistency
Implementing Event-Driven Architecture: Best Practices
1. Design Events Carefully
Events should be:
- Meaningful: Represent something significant in the domain
- Complete: Contain all necessary information for consumers
- Immutable: Never change after creation
- Versioned: Support schema evolution
Example of a well-designed event:
{
"eventId": "e8f8d73b-3819-4a7a-8edb-2983c69e673a",
"eventType": "OrderCreated",
"version": "1.0",
"timestamp": "2025-02-08T10:30:45.123Z",
"data": {
"orderId": "ord-12345",
"customerId": "cust-6789",
"items": [
{
"productId": "prod-101",
"quantity": 2,
"unitPrice": 29.99
},
{
"productId": "prod-202",
"quantity": 1,
"unitPrice": 49.99
}
],
"totalAmount": 109.97,
"shippingAddress": {
"street": "123 Main St",
"city": "Springfield",
"state": "IL",
"zipCode": "62704",
"country": "USA"
}
},
"metadata": {
"source": "order-service",
"correlationId": "req-abcdef",
"traceId": "trace-123456"
}
}