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

  1. Always implement health checks
  2. Use circuit breakers for external calls
  3. Implement proper logging with correlation IDs
  4. Set resource limits in Kubernetes
  5. Use ConfigMaps and Secrets for configuration
  6. Implement graceful shutdown

Building production-ready microservices requires attention to detail and understanding of distributed systems challenges.