Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Domain Event Pattern with Java issue Microservice pattern: Domain event #2670 #3147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions Microservice-pattern-Domain-event/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Microservice Pattern: Domain Event

## Overview
The Domain Event design pattern enables reliable communication between microservices in a decoupled manner. This pattern allows services to react to state changes in other services without tightly coupling them, enhancing scalability and maintainability.

## Intent of the Domain Event Pattern
The Domain Event pattern decouples services by allowing one service to notify others of significant state changes. When an important change occurs, a domain event is published, and other services can listen and react to it.

## Detailed Explanation

### Real-World Example
In an e-commerce system, when a customer places an order, the `OrderService` creates an `OrderCreatedEvent`. This event is published, and other services like Inventory and Payment can listen for it and act accordingly (e.g., updating inventory, processing payment).

### In Plain Words
When an important event happens in one service (e.g., order creation), it publishes a Domain Event. Other services can subscribe to these events and react without directly communicating with each other.

---

## Example of the Domain Event Pattern in Action

Consider a system with the following services:
- **OrderService**: Creates orders.
- **InventoryService**: Updates stock based on orders.
- **PaymentService**: Processes payments after order creation.

When an order is created in the `OrderService`, an `OrderCreatedEvent` is published. Other services listen for this event and act accordingly.

---

## Implementation in Java

### 1. Domain Event Class
This class serves as the base for all domain events and includes the timestamp.

```java
public abstract class DomainEvent {
private final LocalDateTime timestamp;

public DomainEvent() {
this.timestamp = LocalDateTime.now();
}

public LocalDateTime getTimestamp() {
return timestamp;
}
}
```
### 2. Event Publisher
The `EventPublisher` component is responsible for publishing domain events.

```java
@Component
public class EventPublisher {
private final ApplicationEventPublisher applicationEventPublisher;

public EventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void publish(DomainEvent event) {
applicationEventPublisher.publishEvent(event);
}
}

```
### 3. Event Listener
The `OrderCreatedListener` listens for the `OrderCreatedEvent` and processes it when it occurs.

```java
@Component
public class OrderCreatedListener {
@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
System.out.println("Handling Order Created Event: " + event.getOrderId());
// Example logic: Notify another service or update the database
}
}
```

### 4. Order Service
The `OrderService` publishes the `OrderCreatedEvent` when an order is created.

```java
@Service
public class OrderService {
private final EventPublisher eventPublisher;

public OrderService(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}

public void createOrder(String orderId) {
System.out.println("Order created with ID: " + orderId);
OrderCreatedEvent event = new OrderCreatedEvent(orderId);
eventPublisher.publish(event);
}
}
```

### 5. Order Created Event
The `OrderCreatedEvent` extends `DomainEvent` and represents the creation of an order.

```java
public class OrderCreatedEvent extends DomainEvent {
private final String orderId;

public OrderCreatedEvent(String orderId) {
this.orderId = orderId;
}

public String getOrderId() {
return orderId;
}
}

```
### 6. When to Use the Domain Event Pattern
This pattern is ideal for scenarios where:
- Services need to react to state changes in other services without tight coupling.
- Asynchronous communication between services is preferred.
- Scalability is crucial, allowing services to independently scale and evolve.

---

### 7. Benefits and Trade-offs of the Domain Event Pattern

#### Benefits:
1. **Decoupling**: Services are decoupled, making maintenance and independent scaling easier.
2. **Scalability**: Services scale independently, based on individual requirements.
3. **Asynchronous Processing**: Event-driven communication supports asynchronous workflows, enhancing system responsiveness.

#### Trade-offs:
1. **Eventual Consistency**: As events are processed asynchronously, eventual consistency must be handled carefully.
2. **Complexity**: Adding events and listeners increases system complexity, particularly for failure handling and retries.
3. **Debugging**: Asynchronous and decoupled communication can make tracing data flow across services more challenging.

---

### 8. Example Flow

1. **Order Created**: The `OrderService` creates a new order.
2. **Publish Event**: The `OrderCreatedEvent` is published by the `EventPublisher`.
3. **Event Listener**: The `OrderCreatedListener` listens for the event and executes relevant logic (e.g., notifying other services).
4. **Outcome**: Other services (e.g., Inventory, Payment) react to the event accordingly.

---

### 9. References and Credits
- *Domain-Driven Design* by Eric Evans
- *Building Microservices* by Sam Newman
- *Microservices Patterns* by Chris Richardson

For more information on Domain Event patterns and best practices, refer to the above books.
73 changes: 73 additions & 0 deletions Microservice-pattern-Domain-event/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.iluwatar</groupId>
<artifactId>Microservice-pattern-Domain-event</artifactId>
<version>1.0-SNAPSHOT</version>

<!-- Use Spring Boot Parent for Dependency Management -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- Lookup parent from Maven Central -->
</parent>

<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Spring Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>

<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.iluwatar;

import java.time.LocalDateTime;

public abstract class DomainEvent {
private final LocalDateTime timestamp;

public DomainEvent() {
this.timestamp = LocalDateTime.now();
}

public LocalDateTime getTimestamp() {
return timestamp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.iluwatar;

import org.springframework.stereotype.Component;

@Component
public class EventPublisher {

public void publishEvent(OrderCreatedEvent event) {
// Simulate publishing the event
System.out.println("Event published: " + event.getOrderId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.iluwatar;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);

// Simulate creating an order
OrderService orderService = SpringContext.getBean(OrderService.class);
orderService.createOrder("12345");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.iluwatar;

public class OrderCreatedEvent {

private final String orderId;

public OrderCreatedEvent(String orderId) {
this.orderId = orderId;
}

public String getOrderId() {
return orderId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.iluwatar;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderCreatedListener {

private final EventPublisher eventPublisher;

@Autowired
public OrderCreatedListener(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}

public void handleOrderCreatedEvent(OrderCreatedEvent event) {
// Process the event
System.out.println("Processing order: " + event.getOrderId());
eventPublisher.publishEvent(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.iluwatar;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

private final OrderCreatedListener orderCreatedListener;

@Autowired
public OrderService(OrderCreatedListener orderCreatedListener) {
this.orderCreatedListener = orderCreatedListener;
}

public void createOrder(String orderId) {
OrderCreatedEvent event = new OrderCreatedEvent(orderId);
orderCreatedListener.handleOrderCreatedEvent(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.iluwatar;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class SpringContext {
private static ApplicationContext context;

public SpringContext(ApplicationContext applicationContext) {
context = applicationContext;
}

public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.iluwatar;

import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.*;

class EventPublisherTest {

@Test
void testPublishEvent() {
EventPublisher eventPublisher = new EventPublisher();
OrderCreatedEvent event = new OrderCreatedEvent("12345");
eventPublisher.publishEvent(event);
}
}
Loading
Loading