🏗️ Microservices Distributed: Architectural Principles & Patterns
1. 🎯 Core Architectural Principles
1.1 Single Responsibility
Concept: Each microservice should focus on a single business capability.
Key Aspects:
- One service = one business capability
- Independent lifecycle
- Clear boundaries
- Autonomous teams
Example Implementation:
// Good: Order Service focusing only on order management
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {...}
public Order updateOrder(String orderId, OrderUpdate update) {...}
public void cancelOrder(String orderId) {...}
}
// Bad: Mixing different business capabilities
@Service
public class OrderAndInventoryService {
public Order createOrder(OrderRequest request) {...}
public void updateInventory(String productId, int quantity) {...} // Should be in separate service
}
1.2 Service Autonomy 🔓
Concept: Services should be able to operate independently.
Implementation Aspects:
- Private databases
- Independent deployment
- Asynchronous communication
- Local transactions
Example:
@Service
public class CustomerService {
private final CustomerRepository customerRepository; // Private database
private final EventPublisher eventPublisher;
public Customer updateCustomer(CustomerUpdate update) {
// Local transaction
@Transactional
Customer customer = customerRepository.update(update);
// Async communication
eventPublisher.publish(new CustomerUpdatedEvent(customer));
return customer;
}
}
2. 🌟 Essential Distributed Patterns
2.1 Service Discovery Pattern
Concept: Enables services to find and communicate with each other dynamically.
Components:
- Service Registry
- Service Registration
- Service Discovery Client
Implementation Using Netflix Eureka:
@EnableEurekaServer
@SpringBootApplication
public class ServiceRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryApplication.class, args);
}
}
// Client Service Registration
@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
2.2 Circuit Breaker Pattern 🔌
Concept: Prevents cascading failures by failing fast when a service is unavailable.
States:
- Closed (normal operation)
- Open (failure state)
- Half-Open (testing recovery)
Implementation Using Resilience4j:
@Service
public class OrderService {
private final PaymentServiceClient paymentClient;
@CircuitBreaker(name = "paymentService",
fallbackMethod = "paymentFallback")
public PaymentResponse processPayment(OrderPayment payment) {
return paymentClient.processPayment(payment);
}
private PaymentResponse paymentFallback(OrderPayment payment,
Exception e) {
// Fallback logic
return new PaymentResponse.offline()
.withOrderId(payment.getOrderId())
.withStatus(PENDING);
}
}
2.3 Event-Driven Communication Pattern 📨
Concept: Services communicate through events, enabling loose coupling.
Types:
- Domain Events
- Integration Events
- Command Events
Implementation Using Apache Kafka:
@Service
public class OrderEventHandler {
@KafkaListener(topics = "order-events")
public void handleOrderEvent(OrderEvent event) {
switch (event.getType()) {
case ORDER_CREATED:
processNewOrder(event);
break;
case ORDER_CANCELLED:
handleCancellation(event);
break;
case ORDER_UPDATED:
updateOrderStatus(event);
break;
}
}
private void processNewOrder(OrderEvent event) {
// Business logic for new orders
OrderProcessor.createNewOrder(event.getOrderDetails());
// Emit subsequent events
eventPublisher.publish(new InventoryCheckEvent(event.getOrderId()));
}
}
2.4 API Gateway Pattern 🚪
Concept: Single entry point for all client-to-microservice communications.
Responsibilities:
- Request routing
- Authentication
- Load balancing
- Response aggregation
- Circuit breaking
Implementation Using Spring Cloud Gateway:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(
RouteLocatorBuilder builder) {
return builder.routes()
.route("order_service", r -> r
.path("/api/orders/**")
.filters(f -> f
.circuitBreaker(config -> config
.setName("orderServiceCircuitBreaker")
.setFallbackUri("forward:/fallback"))
.retry(config -> config
.setRetries(3)
.setStatuses(HttpStatus.BAD_GATEWAY)))
.uri("lb://order-service"))
.build();
}
}
3. 🛠️ Implementation Best Practices
3.1 Data Management
Principles:
- Database per service
- Event sourcing for data consistency
- CQRS when needed
- Local transactions only
Example of Database per Service:
// Order Service Database Configuration
@Configuration
@EnableJpaRepositories(
basePackages = "com.company.orderservice.repository",
entityManagerFactoryRef = "orderEntityManager"
)
public class OrderDatabaseConfig {
@Bean
@ConfigurationProperties(prefix="spring.datasource.orders")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}
}
// Customer Service Database Configuration
@Configuration
@EnableJpaRepositories(
basePackages = "com.company.customerservice.repository",
entityManagerFactoryRef = "customerEntityManager"
)
public class CustomerDatabaseConfig {
@Bean
@ConfigurationProperties(prefix="spring.datasource.customers")
public DataSource customerDataSource() {
return DataSourceBuilder.create().build();
}
}
3.2 Resilience Patterns
Key Patterns:
- Retry Pattern
- Bulkhead Pattern
- Timeout Pattern
- Circuit Breaker Pattern
Implementation Example:
@Service
public class ResilientService {
@Retry(name = "serviceA")
@Bulkhead(name = "serviceA")
@TimeLimiter(name = "serviceA")
@CircuitBreaker(name = "serviceA")
public CompletableFuture<Response> resilientCall() {
return CompletableFuture.supplyAsync(() -> {
// Service call
return externalService.call();
});
}
}
4. 🎯 Anti-Patterns to Avoid
4.1 Distributed Monolith
What It Is: Microservices that are tightly coupled and must be deployed together.
How to Avoid:
- Maintain service independence
- Use asynchronous communication
- Implement proper service boundaries
- Avoid shared databases
4.2 Direct Database Access
What It Is: Services accessing other services' databases directly.
How to Fix:
// BAD
@Service
public class OrderService {
@Autowired
CustomerRepository customerRepo; // Directly accessing Customer DB
public Order createOrder(OrderRequest request) {
Customer customer = customerRepo.findById(request.getCustomerId());
// Process order...
}
}
// GOOD
@Service
public class OrderService {
private final CustomerClient customerClient;
public Order createOrder(OrderRequest request) {
CustomerDTO customer = customerClient.getCustomer(request.getCustomerId());
// Process order...
}
}
5. 📊 Monitoring and Observability
5.1 Distributed Tracing
Implementation Using Spring Cloud Sleuth and Zipkin:
@RestController
public class OrderController {
private final Tracer tracer;
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable String id) {
Span span = tracer.currentSpan();
span.tag("orderId", id);
// Process request
Order order = orderService.findById(id);
span.tag("orderStatus", order.getStatus());
return order;
}
}
5.2 Health Checks
Implementation:
@Component
public class OrderServiceHealthIndicator implements HealthIndicator {
private final OrderRepository repository;
@Override
public Health health() {
try {
repository.checkConnection();
return Health.up()
.withDetail("database", "responsive")
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}
6. 📚 Further Reading
- "Building Microservices" by Sam Newman
- "Microservices Patterns" by Chris Richardson
- "Domain-Driven Design" by Eric Evans
- Spring Cloud Documentation
- Netflix OSS Documentation