After architecting and deploying over 50 microservices in production, I have learned what it takes to build truly production-ready services. Here is my comprehensive guide.
Architecture Overview
A well-designed microservice should follow these principles:
- Single Responsibility
- Independently Deployable
- Decentralized Data Management
- Fault Tolerant
Project Structure
order-service/
├── src/main/java/
│ └── com/example/order/
│ ├── OrderServiceApplication.java
│ ├── controller/
│ │ └── OrderController.java
│ ├── service/
│ │ └── OrderService.java
│ ├── repository/
│ │ └── OrderRepository.java
│ ├── model/
│ │ └── Order.java
│ ├── dto/
│ │ └── OrderDTO.java
│ ├── exception/
│ │ └── GlobalExceptionHandler.java
│ └── config/
│ └── KafkaConfig.java
├── src/main/resources/
│ └── application.yml
├── Dockerfile
└── k8s/
├── deployment.yaml
└── service.yaml
Essential Components
1. Health Checks
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
return Health.up()
.withDetail("database", "PostgreSQL")
.withDetail("status", "Connected")
.build();
} catch (SQLException e) {
return Health.down()
.withException(e)
.build();
}
}
}
2. Circuit Breaker Pattern
@Service
public class PaymentService {
@CircuitBreaker(name = "payment", fallbackMethod = "fallbackPayment")
@Retry(name = "payment")
public PaymentResponse processPayment(PaymentRequest request) {
return paymentClient.process(request);
}
private PaymentResponse fallbackPayment(PaymentRequest request, Exception e) {
log.error("Payment service unavailable, queuing for retry", e);
return PaymentResponse.pending(request.getOrderId());
}
}
3. Distributed Tracing
# application.yml
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
spec:
containers:
- name: order-service
image: order-service:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
Best Practices Summary
- Always implement health checks
- Use circuit breakers for external calls
- Implement proper logging with correlation IDs
- Set resource limits in Kubernetes
- Use ConfigMaps and Secrets for configuration
- Implement graceful shutdown
Building production-ready microservices requires attention to detail and understanding of distributed systems challenges.