Skip to content

Commit 69846e9

Browse files
Introducing CQRS pattern with Oracle Database TxEventQ
Introducing CQRS pattern with Oracle Database TxEventQ
2 parents d0e6ec7 + a276fe2 commit 69846e9

30 files changed

+839
-0
lines changed

Oracle_CQRS/pom.xml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>3.4.5</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<artifactId>Oracle_CQRS</artifactId>
12+
13+
<properties>
14+
<java.version>21</java.version>
15+
<lombok.version>1.18.38</lombok.version>
16+
<springdoc-openapi.version>2.8.6</springdoc-openapi.version>
17+
<oracle-spring-boot-starter-ucp.version>25.1.0</oracle-spring-boot-starter-ucp.version>
18+
<oracle-spring-boot-starter-aqjms.version>25.1.0</oracle-spring-boot-starter-aqjms.version>
19+
</properties>
20+
21+
<dependencies>
22+
<dependency>
23+
<groupId>org.springframework.boot</groupId>
24+
<artifactId>spring-boot-starter-actuator</artifactId>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-starter-data-jpa</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-web</artifactId>
33+
</dependency>
34+
35+
<dependency>
36+
<groupId>com.oracle.database.jdbc</groupId>
37+
<artifactId>ojdbc11</artifactId>
38+
<scope>runtime</scope>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>org.projectlombok</groupId>
43+
<artifactId>lombok</artifactId>
44+
<version>${lombok.version}</version>
45+
<scope>provided</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-test</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
53+
<dependency>
54+
<groupId>com.oracle.database.spring</groupId>
55+
<artifactId>oracle-spring-boot-starter-ucp</artifactId>
56+
<version>${oracle-spring-boot-starter-ucp.version}</version>
57+
</dependency>
58+
59+
<dependency>
60+
<groupId>org.springdoc</groupId>
61+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
62+
<version>${springdoc-openapi.version}</version>
63+
</dependency>
64+
65+
<dependency>
66+
<groupId>org.springframework.boot</groupId>
67+
<artifactId>spring-boot-starter-data-jdbc</artifactId>
68+
</dependency>
69+
<dependency>
70+
<groupId>com.oracle.database.spring</groupId>
71+
<artifactId>oracle-spring-boot-starter-aqjms</artifactId>
72+
<version>${oracle-spring-boot-starter-aqjms.version}</version>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>org.springframework.boot</groupId>
77+
<artifactId>spring-boot-starter-validation</artifactId>
78+
</dependency>
79+
80+
81+
</dependencies>
82+
83+
<build>
84+
<plugins>
85+
<plugin>
86+
<groupId>org.apache.maven.plugins</groupId>
87+
<artifactId>maven-compiler-plugin</artifactId>
88+
<configuration>
89+
<annotationProcessorPaths>
90+
<path>
91+
<groupId>org.projectlombok</groupId>
92+
<artifactId>lombok</artifactId>
93+
<version>${lombok.version}</version>
94+
</path>
95+
</annotationProcessorPaths>
96+
</configuration>
97+
</plugin>
98+
<plugin>
99+
<groupId>org.springframework.boot</groupId>
100+
<artifactId>spring-boot-maven-plugin</artifactId>
101+
<configuration>
102+
<excludes>
103+
<exclude>
104+
<groupId>org.projectlombok</groupId>
105+
<artifactId>lombok</artifactId>
106+
<version>${lombok.version}</version>
107+
</exclude>
108+
</excludes>
109+
</configuration>
110+
</plugin>
111+
</plugins>
112+
</build>
113+
114+
</project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.example.oracle.cqrs;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
7+
import org.springframework.jms.support.converter.MessageType;
8+
9+
10+
@SpringBootApplication
11+
public class OracleCqrsApplication {
12+
13+
public static void main(String[] args) {
14+
SpringApplication.run(OracleCqrsApplication.class, args);
15+
}
16+
17+
18+
@Bean
19+
public MappingJackson2MessageConverter jacksonJmsMessageConverter() {
20+
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
21+
converter.setTargetType(MessageType.TEXT); // send as TEXTMessage (JSON)
22+
converter.setTypeIdPropertyName("_type");
23+
return converter;
24+
}
25+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.example.oracle.cqrs.command.aggregates;
2+
3+
import jakarta.transaction.Transactional;
4+
import lombok.*;
5+
6+
import org.example.oracle.cqrs.command.commands.*;
7+
import org.example.oracle.cqrs.common.events.*;
8+
import org.example.oracle.cqrs.command.producers.EventProducer;
9+
import org.example.oracle.cqrs.command.repository.EventStoreRepository;
10+
import org.example.oracle.cqrs.common.enums.AccountStatus;
11+
import org.springframework.jms.annotation.JmsListener;
12+
import org.springframework.stereotype.Component;
13+
14+
import java.util.UUID;
15+
16+
17+
@Component
18+
@Transactional
19+
public class AccountAggregate {
20+
@Getter
21+
private String accountId;
22+
@Getter
23+
private double currentBalance;
24+
@Getter
25+
private String currency;
26+
@Getter
27+
private AccountStatus status;
28+
29+
private EventProducer eventProducer;
30+
private EventStoreRepository eventStoreRepository;
31+
32+
public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
33+
this.eventProducer = eventProducer;
34+
this.eventStoreRepository = eventStoreRepository;
35+
}
36+
37+
38+
@JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
39+
void handleCommand(BaseCommand command) {
40+
41+
BaseEvent event;
42+
43+
switch (command) {
44+
case CreateAccountCommand createAccountCommand -> {
45+
System.out.println("Handling create: " + command);
46+
if (createAccountCommand.getInitialBalance() < 0)
47+
throw new IllegalArgumentException("Initial balance is negative");
48+
49+
event = new AccountCreatedEvent(UUID.randomUUID().toString(), createAccountCommand.getInitialBalance(), createAccountCommand.getCurrency(), AccountStatus.CREATED, createAccountCommand.getAccountId());
50+
51+
}
52+
case DebitAccountCommand debitAccountCommand -> {
53+
System.out.println("Handling debit: " + command);
54+
if (debitAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
55+
56+
event = new AccountDebitedEvent(UUID.randomUUID().toString(), debitAccountCommand.getAccountId(), debitAccountCommand.getCurrency(), debitAccountCommand.getAmount());
57+
58+
59+
}
60+
case CreditAccountCommand creditAccountCommand -> {
61+
System.out.println("Handling debit: " + command);
62+
if (creditAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
63+
64+
event = new AccountCreditedEvent(UUID.randomUUID().toString(), creditAccountCommand.getCurrency(), creditAccountCommand.getAmount(), creditAccountCommand.getAccountId());
65+
66+
67+
}
68+
case UpdateAccountStatusCommand updateAccountStatusCommand -> {
69+
System.out.println("Handling debit: " + command);
70+
event = new AccountStatusUpdatedEvent(UUID.randomUUID().toString(), updateAccountStatusCommand.getAccountStatus(), updateAccountStatusCommand.getAccountId());
71+
72+
}
73+
74+
default -> throw new IllegalStateException("Unexpected value: " + command);
75+
}
76+
77+
eventProducer.enqueue(event);
78+
eventStoreRepository.save(event);
79+
80+
}
81+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.example.oracle.cqrs.command.commands;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
7+
8+
@AllArgsConstructor @NoArgsConstructor
9+
public class BaseCommand<T>{
10+
@Getter
11+
private T id;
12+
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.example.oracle.cqrs.command.commands;
2+
3+
import lombok.*;
4+
5+
@Getter @NoArgsConstructor
6+
public class CreateAccountCommand extends BaseCommand<String> {
7+
private double initialBalance;
8+
private String currency;
9+
private String accountId ;
10+
11+
public CreateAccountCommand(String id, double initialBalance, String currency, String accountId) {
12+
super(id);
13+
this.initialBalance = initialBalance;
14+
this.currency = currency;
15+
this.accountId = accountId;
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.example.oracle.cqrs.command.commands;
2+
3+
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
7+
@Getter
8+
@NoArgsConstructor
9+
public class CreditAccountCommand extends BaseCommand<String> {
10+
private String accountId;
11+
private double amount;
12+
private String currency;
13+
14+
public CreditAccountCommand(String id, String accountId, double amount, String currency) {
15+
super(id);
16+
this.accountId = accountId;
17+
this.amount = amount;
18+
this.currency = currency;
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.example.oracle.cqrs.command.commands;
2+
3+
import lombok.*;
4+
5+
@Getter
6+
@NoArgsConstructor
7+
public class DebitAccountCommand extends BaseCommand<String> {
8+
private String accountId;
9+
private double amount;
10+
private String currency;
11+
12+
public DebitAccountCommand(String id, String accountId, double amount, String currency) {
13+
super(id);
14+
this.accountId = accountId;
15+
this.amount = amount;
16+
this.currency = currency;
17+
}
18+
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.example.oracle.cqrs.command.commands;
2+
3+
import lombok.Getter;
4+
import lombok.NoArgsConstructor;
5+
import org.example.oracle.cqrs.common.enums.AccountStatus;
6+
7+
@Getter
8+
@NoArgsConstructor
9+
public class UpdateAccountStatusCommand extends BaseCommand<String> {
10+
private String accountId;
11+
private AccountStatus accountStatus;
12+
13+
public UpdateAccountStatusCommand(String id, String accountId, AccountStatus accountStatus) {
14+
super(id);
15+
this.accountId = accountId;
16+
this.accountStatus = accountStatus;
17+
}
18+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.example.oracle.cqrs.command.controlles;
2+
3+
4+
import jakarta.validation.Valid;
5+
import org.example.oracle.cqrs.command.commands.CreateAccountCommand;
6+
import org.example.oracle.cqrs.command.commands.CreditAccountCommand;
7+
import org.example.oracle.cqrs.command.commands.DebitAccountCommand;
8+
import org.example.oracle.cqrs.command.commands.UpdateAccountStatusCommand;
9+
import org.example.oracle.cqrs.command.producers.CommandsProducer;
10+
import org.example.oracle.cqrs.command.repository.EventStoreRepository;
11+
import org.example.oracle.cqrs.common.Dtos.CreateAccountDTO;
12+
import org.example.oracle.cqrs.common.Dtos.CreditAccountDTO;
13+
import org.example.oracle.cqrs.common.Dtos.DebitAccountDTO;
14+
import org.example.oracle.cqrs.common.Dtos.UpdateAccountStatusDTO;
15+
import org.example.oracle.cqrs.common.events.BaseEvent;
16+
import org.springframework.http.HttpStatus;
17+
import org.springframework.http.ResponseEntity;
18+
import org.springframework.web.bind.annotation.*;
19+
20+
import java.util.List;
21+
import java.util.UUID;
22+
23+
@RestController
24+
@RequestMapping("/api/commands")
25+
public class AccountCommandRest {
26+
27+
private CommandsProducer commandsProducer;
28+
private EventStoreRepository eventStoreRepository;
29+
30+
AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
31+
this.commandsProducer = commandsProducer;
32+
this.eventStoreRepository = eventStoreRepository;
33+
}
34+
35+
@PostMapping("/create")
36+
public ResponseEntity createAccount(@Valid @RequestBody CreateAccountDTO request) {
37+
String accountId = UUID.randomUUID().toString();
38+
commandsProducer.enqueue(new CreateAccountCommand(UUID.randomUUID().toString(), request.getInitialBalance(), request.getCurrency(), accountId));
39+
40+
return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/status/" + accountId).build();
41+
}
42+
43+
@PostMapping("/debit")
44+
public ResponseEntity debitAccount(@Valid @RequestBody DebitAccountDTO request) {
45+
commandsProducer.enqueue(new DebitAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
46+
47+
return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
48+
}
49+
50+
@PostMapping("/credit")
51+
public ResponseEntity creditAccount(@Valid @RequestBody CreditAccountDTO request) {
52+
commandsProducer.enqueue(new CreditAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
53+
54+
return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
55+
}
56+
57+
@PutMapping("/updateStatus")
58+
public ResponseEntity updateStatus(@Valid @RequestBody UpdateAccountStatusDTO request) {
59+
commandsProducer.enqueue(new UpdateAccountStatusCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAccountStatus()));
60+
return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/status/" + request.getAccountId()).build();
61+
}
62+
63+
64+
@GetMapping("allEvents")
65+
public List<BaseEvent> getAllEvents() {
66+
return eventStoreRepository.findAll();
67+
}
68+
69+
@ExceptionHandler(Exception.class)
70+
public String exceptionHandler(Exception exception) {
71+
return exception.getMessage();
72+
}
73+
74+
75+
}

0 commit comments

Comments
 (0)