Top 5 Microservices Design Patterns in Java with Code Examples (2025 Guide)

1. API Gateway Pattern

An API Gateway acts as a single entry point for all clients, routing requests to the appropriate microservices. It can handle authentication, load balancing, caching, and request shaping. Some of the api gateways can also combine multiple services into a coarse-grained service and can do response filterings

We will use Spring framework for this article. You can set up an API Gateway in a Spring Boot application using Spring Cloud Gateway.

  1. Add Dependencies: Include the necessary dependencies in your pom.xml:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>Code language: HTML, XML (xml)
  1. Configure Routes: Define routing rules in your application.yml:
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/users/**
        - id: order-service
          uri: lb://ORDER-SERVICE
          predicates:
            - Path=/orders/**Code language: JavaScript (javascript)

In this configuration, requests to /users/** are routed to the USER-SERVICE, and requests to /orders/** are routed to the ORDER-SERVICE.

3. Enable Discovery Client: If you’re using a service registry like Eureka, Apicurio registry etc annotate your main application class with @EnableDiscoveryClient annotation.

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}Code language: PHP (php)

Benefits:

  • Better management and configuration
  • Centralizes cross-cutting concerns like authentication and logging.
  • Simplifies api interactions by consolidating multiple service endpoints.
  • Version management

2. Database per Service Pattern

Detailed Explanation:

In this pattern, each microservice has its dedicated database, ensuring loose coupling and autonomy. This approach allows services to evolve independently without affecting others. Services can use any database based on their needs. For example, a service might use an RDBMS whereas another service may decide to use a NOSQL Db

Java Implementation Example:

Consider a CustomerService with its database.

Configure Database Connection: In application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/customerdb
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=updateCode language: JavaScript (javascript)

Define Entity and Repository:

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    // Getters and setters
}

public interface CustomerRepository 
  extends JpaRepository<Customer, Long> {
}Code language: PHP (php)

Service Layer:

@Service
public class CustomerService {
    @Autowired
    private CustomerRepository repository;

    public List<Customer> getAllCustomers() {
        return repository.findAll();
    }
    // Other methods
}Code language: PHP (php)

Similarly, other microservices will have their database.

Considerations:

  • Service orchestration challenges
  • Devops challenges
  • Maintaining data consistency across services can be challenging. Techniques like the Saga pattern can help manage distributed transactions.
  • Cross-service queries become complex; solutions include API composition or using a data warehouse.

3. Event Sourcing

Event Sourcing involves storing the state changes of an application as a sequence of events. Instead of persisting the current state, you persist the events that led to that state. This approach provides a reliable audit log and can reconstruct past states by replaying events.

Java Implementation Example:

Using the Axon Framework, you can implement Event Sourcing in a Spring Boot application.

Add Dependencies:

<dependency>
    <groupId>org.axonframework</groupId>
    <artifactId>axon-spring-boot-starter</artifactId>
    <version>4.5</version>
</dependency>Code language: HTML, XML (xml)

Define Commands and Events:

public class CreateAccountCommand {
    @TargetAggregateIdentifier
    private String accountId;
    private double initialBalance;
    // Constructor, getters
}

public class AccountCreatedEvent {
    private String accountId;
    private double initialBalance;
    // Constructor, getters
}Code language: PHP (php)

Aggregate Root:

@Aggregate
public class AccountAggregate {
    @AggregateIdentifier
    private String accountId;
    private double balance;

    @CommandHandler
    public AccountAggregate(CreateAccountCommand command) {
        AggregateLifecycle.apply(
           new AccountCreatedEvent(command.getAccountId(), 
            command.getInitialBalance()));
    }

    @EventSourcingHandler
    public void on(AccountCreatedEvent event) {
        this.accountId = event.getAccountId();
        this.balance = event.getInitialBalance();
    }
    // Other command and event handlers
}Code language: PHP (php)

Benefits:

  • Provides a reliable audit trail.
  • Enables reconstruction of past states for debugging or analysis.

Considerations:

  • Requires careful design to manage event versioning and storage.
  • Eventual consistency can introduce complexity in read models.

4. Saga Pattern

The Saga pattern manages distributed transactions by breaking them into a series of smaller, independent transactions, each with its own compensating action in case of failure. There are two main approaches:

  • Choreography: Each service listens for events and reacts accordingly. Individual services doesn’t know the global picture , ie one service doesn’t know whether another service has failed. If there is an issue in event propagation, then the business flow can remain stuck
  • Orchestration: A central coordinator manages the saga’s flow. This orchestrator tracks the different services and knows whether the services are success or failure and it can trigger alternate flows, retries etc. Provides a global picture of the business flow and its current and final state. Use standard workflow management tools for orchestration.

Java Implementation Example (Orchestration with Spring Boot and Axon Framework):

Define Commands and Events:

public class CreateOrderCommand {
    @TargetAggregateIdentifier
    private String orderId;
    private String product;
    private int quantity;
    // Constructors, getters
}

public class OrderCreatedEvent {
    private String orderId;
    private String product;
    private int quantity;
    // Constructors, getters
}Code language: PHP (php)

Order Aggregate (Coordinator/Orchestrator):

@Aggregate
public class OrderAggregate {

    @AggregateIdentifier
    private String orderId;

    public OrderAggregate() {}

    @CommandHandler
    public OrderAggregate(CreateOrderCommand cmd) {
        AggregateLifecycle.apply(new OrderCreatedEvent(cmd.getOrderId(), cmd.getProduct(), cmd.getQuantity()));
    }

    @EventSourcingHandler
    public void on(OrderCreatedEvent event) {
        this.orderId = event.getOrderId();
    }
}Code language: PHP (php)

Saga Orchestrator (Listen and Trigger Steps):

@Saga
public class OrderProcessingSaga {

    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void on(OrderCreatedEvent event) {
        // call inventory service
        ReserveProductCommand command = new ReserveProductCommand(event.getOrderId(), event.getProduct(), event.getQuantity());
        CommandGateway.send(command);
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void on(ProductReservedEvent event) {
        // call payment service next
    }

    @EndSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void on(PaymentSuccessfulEvent event) {
        // finalize the saga
    }
}Code language: PHP (php)

5. Circuit Breaker Pattern

This pattern is used to prevent cascading failures when one microservice is down. Essential to build scalable and resillient microservices.

Java Implementation with Resilience4j

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
</dependency>

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/products/{id}")
    @CircuitBreaker(name = "productService", fallbackMethod = "fallbackGetProduct")
    public Product getProduct(@PathVariable String id) {
        return productService.fetchProduct(id);
    }

    public Product fallbackGetProduct(String id, Throwable t) {
        return new Product(id, "Default Product", 0);
    }
}Code language: PHP (php)

In the above example, if the productService is down or not responding, then the Circut breaker will use the fall back service and return the response.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *


 We break down complex topics into clear, actionable content. Built for developers, learners, and curious minds.


Socail Connect

theStemBookDev

about science, tech, engineering , programming and more