Skip to content

Commit df3a42f

Browse files
author
Ahmed Khaled
committed
Add Domain Event Pattern with Java examples
1 parent adbddcb commit df3a42f

File tree

27 files changed

+1059
-2
lines changed

27 files changed

+1059
-2
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Microservice Pattern: Domain Event
2+
3+
## Overview
4+
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.
5+
6+
## Intent of the Domain Event Pattern
7+
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.
8+
9+
## Detailed Explanation
10+
11+
### Real-World Example
12+
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).
13+
14+
### In Plain Words
15+
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.
16+
17+
---
18+
19+
## Example of the Domain Event Pattern in Action
20+
21+
Consider a system with the following services:
22+
- **OrderService**: Creates orders.
23+
- **InventoryService**: Updates stock based on orders.
24+
- **PaymentService**: Processes payments after order creation.
25+
26+
When an order is created in the `OrderService`, an `OrderCreatedEvent` is published. Other services listen for this event and act accordingly.
27+
28+
---
29+
30+
## Implementation in Java
31+
32+
### 1. Domain Event Class
33+
This class serves as the base for all domain events and includes the timestamp.
34+
35+
```java
36+
public abstract class DomainEvent {
37+
private final LocalDateTime timestamp;
38+
39+
public DomainEvent() {
40+
this.timestamp = LocalDateTime.now();
41+
}
42+
43+
public LocalDateTime getTimestamp() {
44+
return timestamp;
45+
}
46+
}
47+
```
48+
### 2. Event Publisher
49+
The `EventPublisher` component is responsible for publishing domain events.
50+
51+
```java
52+
@Component
53+
public class EventPublisher {
54+
private final ApplicationEventPublisher applicationEventPublisher;
55+
56+
public EventPublisher(ApplicationEventPublisher applicationEventPublisher) {
57+
this.applicationEventPublisher = applicationEventPublisher;
58+
}
59+
60+
public void publish(DomainEvent event) {
61+
applicationEventPublisher.publishEvent(event);
62+
}
63+
}
64+
65+
```
66+
### 3. Event Listener
67+
The `OrderCreatedListener` listens for the `OrderCreatedEvent` and processes it when it occurs.
68+
69+
```java
70+
@Component
71+
public class OrderCreatedListener {
72+
@EventListener
73+
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
74+
System.out.println("Handling Order Created Event: " + event.getOrderId());
75+
// Example logic: Notify another service or update the database
76+
}
77+
}
78+
```
79+
80+
### 4. Order Service
81+
The `OrderService` publishes the `OrderCreatedEvent` when an order is created.
82+
83+
```java
84+
@Service
85+
public class OrderService {
86+
private final EventPublisher eventPublisher;
87+
88+
public OrderService(EventPublisher eventPublisher) {
89+
this.eventPublisher = eventPublisher;
90+
}
91+
92+
public void createOrder(String orderId) {
93+
System.out.println("Order created with ID: " + orderId);
94+
OrderCreatedEvent event = new OrderCreatedEvent(orderId);
95+
eventPublisher.publish(event);
96+
}
97+
}
98+
```
99+
100+
### 5. Order Created Event
101+
The `OrderCreatedEvent` extends `DomainEvent` and represents the creation of an order.
102+
103+
```java
104+
public class OrderCreatedEvent extends DomainEvent {
105+
private final String orderId;
106+
107+
public OrderCreatedEvent(String orderId) {
108+
this.orderId = orderId;
109+
}
110+
111+
public String getOrderId() {
112+
return orderId;
113+
}
114+
}
115+
116+
```
117+
### 6. When to Use the Domain Event Pattern
118+
This pattern is ideal for scenarios where:
119+
- Services need to react to state changes in other services without tight coupling.
120+
- Asynchronous communication between services is preferred.
121+
- Scalability is crucial, allowing services to independently scale and evolve.
122+
123+
---
124+
125+
### 7. Benefits and Trade-offs of the Domain Event Pattern
126+
127+
#### Benefits:
128+
1. **Decoupling**: Services are decoupled, making maintenance and independent scaling easier.
129+
2. **Scalability**: Services scale independently, based on individual requirements.
130+
3. **Asynchronous Processing**: Event-driven communication supports asynchronous workflows, enhancing system responsiveness.
131+
132+
#### Trade-offs:
133+
1. **Eventual Consistency**: As events are processed asynchronously, eventual consistency must be handled carefully.
134+
2. **Complexity**: Adding events and listeners increases system complexity, particularly for failure handling and retries.
135+
3. **Debugging**: Asynchronous and decoupled communication can make tracing data flow across services more challenging.
136+
137+
---
138+
139+
### 8. Example Flow
140+
141+
1. **Order Created**: The `OrderService` creates a new order.
142+
2. **Publish Event**: The `OrderCreatedEvent` is published by the `EventPublisher`.
143+
3. **Event Listener**: The `OrderCreatedListener` listens for the event and executes relevant logic (e.g., notifying other services).
144+
4. **Outcome**: Other services (e.g., Inventory, Payment) react to the event accordingly.
145+
146+
---
147+
148+
### 9. References and Credits
149+
- *Domain-Driven Design* by Eric Evans
150+
- *Building Microservices* by Sam Newman
151+
- *Microservices Patterns* by Chris Richardson
152+
153+
For more information on Domain Event patterns and best practices, refer to the above books.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.iluwatar</groupId>
7+
<artifactId>Microservice-pattern-Domain-event</artifactId>
8+
<version>1.0-SNAPSHOT</version>
9+
10+
<!-- Use Spring Boot Parent for Dependency Management -->
11+
<parent>
12+
<groupId>org.springframework.boot</groupId>
13+
<artifactId>spring-boot-starter-parent</artifactId>
14+
<version>3.1.0</version>
15+
<relativePath/> <!-- Lookup parent from Maven Central -->
16+
</parent>
17+
18+
<properties>
19+
<java.version>17</java.version>
20+
<maven.compiler.source>${java.version}</maven.compiler.source>
21+
<maven.compiler.target>${java.version}</maven.compiler.target>
22+
</properties>
23+
24+
<dependencies>
25+
<!-- Spring Boot Starter -->
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-starter</artifactId>
29+
</dependency>
30+
31+
<!-- Spring Context -->
32+
<dependency>
33+
<groupId>org.springframework</groupId>
34+
<artifactId>spring-context</artifactId>
35+
</dependency>
36+
37+
<!-- Spring Boot Starter Test -->
38+
<dependency>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-starter-test</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
44+
<!-- JUnit 5 -->
45+
<dependency>
46+
<groupId>org.junit.jupiter</groupId>
47+
<artifactId>junit-jupiter</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
51+
<!-- Mockito -->
52+
<dependency>
53+
<groupId>org.mockito</groupId>
54+
<artifactId>mockito-core</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.mockito</groupId>
59+
<artifactId>mockito-junit-jupiter</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
</dependencies>
63+
64+
<build>
65+
<plugins>
66+
<!-- Spring Boot Maven Plugin -->
67+
<plugin>
68+
<groupId>org.springframework.boot</groupId>
69+
<artifactId>spring-boot-maven-plugin</artifactId>
70+
</plugin>
71+
</plugins>
72+
</build>
73+
</project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.iluwatar;
2+
3+
import java.time.LocalDateTime;
4+
5+
public abstract class DomainEvent {
6+
private final LocalDateTime timestamp;
7+
8+
public DomainEvent() {
9+
this.timestamp = LocalDateTime.now();
10+
}
11+
12+
public LocalDateTime getTimestamp() {
13+
return timestamp;
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.iluwatar;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
@Component
6+
public class EventPublisher {
7+
8+
public void publishEvent(OrderCreatedEvent event) {
9+
// Simulate publishing the event
10+
System.out.println("Event published: " + event.getOrderId());
11+
}
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.iluwatar;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class Main {
8+
public static void main(String[] args) {
9+
SpringApplication.run(Main.class, args);
10+
11+
// Simulate creating an order
12+
OrderService orderService = SpringContext.getBean(OrderService.class);
13+
orderService.createOrder("12345");
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.iluwatar;
2+
3+
public class OrderCreatedEvent {
4+
5+
private final String orderId;
6+
7+
public OrderCreatedEvent(String orderId) {
8+
this.orderId = orderId;
9+
}
10+
11+
public String getOrderId() {
12+
return orderId;
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.iluwatar;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
public class OrderCreatedListener {
8+
9+
private final EventPublisher eventPublisher;
10+
11+
@Autowired
12+
public OrderCreatedListener(EventPublisher eventPublisher) {
13+
this.eventPublisher = eventPublisher;
14+
}
15+
16+
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
17+
// Process the event
18+
System.out.println("Processing order: " + event.getOrderId());
19+
eventPublisher.publishEvent(event);
20+
}
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.iluwatar;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class OrderService {
8+
9+
private final OrderCreatedListener orderCreatedListener;
10+
11+
@Autowired
12+
public OrderService(OrderCreatedListener orderCreatedListener) {
13+
this.orderCreatedListener = orderCreatedListener;
14+
}
15+
16+
public void createOrder(String orderId) {
17+
OrderCreatedEvent event = new OrderCreatedEvent(orderId);
18+
orderCreatedListener.handleOrderCreatedEvent(event);
19+
}
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.iluwatar;
2+
3+
import org.springframework.context.ApplicationContext;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
public class SpringContext {
8+
private static ApplicationContext context;
9+
10+
public SpringContext(ApplicationContext applicationContext) {
11+
context = applicationContext;
12+
}
13+
14+
public static <T> T getBean(Class<T> beanClass) {
15+
return context.getBean(beanClass);
16+
}
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.iluwatar;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.mockito.Mockito.*;
6+
7+
class EventPublisherTest {
8+
9+
@Test
10+
void testPublishEvent() {
11+
EventPublisher eventPublisher = new EventPublisher();
12+
OrderCreatedEvent event = new OrderCreatedEvent("12345");
13+
eventPublisher.publishEvent(event);
14+
}
15+
}

0 commit comments

Comments
 (0)