Repository: oracle/microservices-datadriven
Pull request #1059: Introducing CQRS pattern with Oracle Database TxEventQ
Author: login: soulaimanGhailan
Labels: name: OCA Verified
description: All contributors have signed the Oracle Contributor Agreement.
Created: 2025-05-07T22:13:26Z | Merged: 2025-05-16T13:45:30Z



Diff:
diff --git a/Oracle_CQRS/pom.xml b/Oracle_CQRS/pom.xml
new file mode 100644
index 000000000..9a78d3382
--- /dev/null
+++ b/Oracle_CQRS/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <artifactId>Oracle_CQRS</artifactId>
+
+    <properties>
+        <java.version>21</java.version>
+        <lombok.version>1.18.38</lombok.version>
+        <springdoc-openapi.version>2.8.6</springdoc-openapi.version>
+        <oracle-spring-boot-starter-ucp.version>25.1.0</oracle-spring-boot-starter-ucp.version>
+        <oracle-spring-boot-starter-aqjms.version>25.1.0</oracle-spring-boot-starter-aqjms.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc11</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.oracle.database.spring</groupId>
+            <artifactId>oracle-spring-boot-starter-ucp</artifactId>
+            <version>${oracle-spring-boot-starter-ucp.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>${springdoc-openapi.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.oracle.database.spring</groupId>
+            <artifactId>oracle-spring-boot-starter-aqjms</artifactId>
+            <version>${oracle-spring-boot-starter-aqjms.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/OracleCqrsApplication.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/OracleCqrsApplication.java
new file mode 100644
index 000000000..f381fcc83
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/OracleCqrsApplication.java
@@ -0,0 +1,25 @@
+package org.example.oracle.cqrs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
+import org.springframework.jms.support.converter.MessageType;
+
+
+@SpringBootApplication
+public class OracleCqrsApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OracleCqrsApplication.class, args);
+    }
+
+
+    @Bean
+    public MappingJackson2MessageConverter jacksonJmsMessageConverter() {
+        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
+        converter.setTargetType(MessageType.TEXT); // send as TEXTMessage (JSON)
+        converter.setTypeIdPropertyName("_type");
+        return converter;
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/aggregates/AccountAggregate.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/aggregates/AccountAggregate.java
new file mode 100644
index 000000000..07ba9bb5a
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/aggregates/AccountAggregate.java
@@ -0,0 +1,81 @@
+package org.example.oracle.cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle.cqrs.command.commands.*;
+import org.example.oracle.cqrs.common.events.*;
+import org.example.oracle.cqrs.command.producers.EventProducer;
+import org.example.oracle.cqrs.command.repository.EventStoreRepository;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        BaseEvent event;
+
+        switch (command) {
+            case CreateAccountCommand createAccountCommand -> {
+                System.out.println("Handling create: " + command);
+                if (createAccountCommand.getInitialBalance() < 0)
+                    throw new IllegalArgumentException("Initial balance is negative");
+
+                event = new AccountCreatedEvent(UUID.randomUUID().toString(), createAccountCommand.getInitialBalance(), createAccountCommand.getCurrency(), AccountStatus.CREATED, createAccountCommand.getAccountId());
+
+            }
+            case DebitAccountCommand debitAccountCommand -> {
+                System.out.println("Handling debit: " + command);
+                if (debitAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
+
+                event = new AccountDebitedEvent(UUID.randomUUID().toString(), debitAccountCommand.getAccountId(), debitAccountCommand.getCurrency(), debitAccountCommand.getAmount());
+
+
+            }
+            case CreditAccountCommand creditAccountCommand -> {
+                System.out.println("Handling debit: " + command);
+                if (creditAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
+
+                event = new AccountCreditedEvent(UUID.randomUUID().toString(), creditAccountCommand.getCurrency(), creditAccountCommand.getAmount(), creditAccountCommand.getAccountId());
+
+
+            }
+            case UpdateAccountStatusCommand updateAccountStatusCommand -> {
+                System.out.println("Handling debit: " + command);
+                event = new AccountStatusUpdatedEvent(UUID.randomUUID().toString(), updateAccountStatusCommand.getAccountStatus(), updateAccountStatusCommand.getAccountId());
+
+            }
+
+            default -> throw new IllegalStateException("Unexpected value: " + command);
+        }
+
+        eventProducer.enqueue(event);
+        eventStoreRepository.save(event);
+
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/BaseCommand.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/BaseCommand.java
new file mode 100644
index 000000000..540595de7
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/BaseCommand.java
@@ -0,0 +1,13 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+
+@AllArgsConstructor @NoArgsConstructor
+public class BaseCommand<T>{
+    @Getter
+    private T id;
+
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreateAccountCommand.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreateAccountCommand.java
new file mode 100644
index 000000000..2494870c6
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreateAccountCommand.java
@@ -0,0 +1,17 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.*;
+
+@Getter @NoArgsConstructor
+public class CreateAccountCommand extends BaseCommand<String> {
+    private double initialBalance;
+    private String currency;
+    private String accountId ;
+
+    public CreateAccountCommand(String id, double initialBalance, String currency, String accountId) {
+        super(id);
+        this.initialBalance = initialBalance;
+        this.currency = currency;
+        this.accountId = accountId;
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreditAccountCommand.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreditAccountCommand.java
new file mode 100644
index 000000000..ee7c80910
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreditAccountCommand.java
@@ -0,0 +1,20 @@
+package org.example.oracle.cqrs.command.commands;
+
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+public class CreditAccountCommand extends BaseCommand<String> {
+    private String accountId;
+    private double amount;
+    private String currency;
+
+    public CreditAccountCommand(String id, String accountId, double amount, String currency) {
+        super(id);
+        this.accountId = accountId;
+        this.amount = amount;
+        this.currency = currency;
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/DebitAccountCommand.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/DebitAccountCommand.java
new file mode 100644
index 000000000..dc62aec61
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/DebitAccountCommand.java
@@ -0,0 +1,19 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.*;
+
+@Getter
+@NoArgsConstructor
+public class DebitAccountCommand extends BaseCommand<String> {
+    private String accountId;
+    private double amount;
+    private String currency;
+
+    public DebitAccountCommand(String id, String accountId, double amount, String currency) {
+        super(id);
+        this.accountId = accountId;
+        this.amount = amount;
+        this.currency = currency;
+    }
+
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/UpdateAccountStatusCommand.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/UpdateAccountStatusCommand.java
new file mode 100644
index 000000000..c16913eae
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/UpdateAccountStatusCommand.java
@@ -0,0 +1,18 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+
+@Getter
+@NoArgsConstructor
+public class UpdateAccountStatusCommand extends BaseCommand<String> {
+    private String accountId;
+    private AccountStatus accountStatus;
+
+    public UpdateAccountStatusCommand(String id, String accountId, AccountStatus accountStatus) {
+        super(id);
+        this.accountId = accountId;
+        this.accountStatus = accountStatus;
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/controlles/AccountCommandRest.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/controlles/AccountCommandRest.java
new file mode 100644
index 000000000..95839fbfd
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/controlles/AccountCommandRest.java
@@ -0,0 +1,75 @@
+package org.example.oracle.cqrs.command.controlles;
+
+
+import jakarta.validation.Valid;
+import org.example.oracle.cqrs.command.commands.CreateAccountCommand;
+import org.example.oracle.cqrs.command.commands.CreditAccountCommand;
+import org.example.oracle.cqrs.command.commands.DebitAccountCommand;
+import org.example.oracle.cqrs.command.commands.UpdateAccountStatusCommand;
+import org.example.oracle.cqrs.command.producers.CommandsProducer;
+import org.example.oracle.cqrs.command.repository.EventStoreRepository;
+import org.example.oracle.cqrs.common.Dtos.CreateAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.CreditAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.DebitAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.UpdateAccountStatusDTO;
+import org.example.oracle.cqrs.common.events.BaseEvent;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/commands")
+public class AccountCommandRest {
+
+    private CommandsProducer commandsProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
+        this.commandsProducer = commandsProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+    @PostMapping("/create")
+    public ResponseEntity createAccount(@Valid @RequestBody CreateAccountDTO request) {
+        String accountId = UUID.randomUUID().toString();
+        commandsProducer.enqueue(new CreateAccountCommand(UUID.randomUUID().toString(), request.getInitialBalance(), request.getCurrency(), accountId));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/status/" + accountId).build();
+    }
+
+    @PostMapping("/debit")
+    public ResponseEntity debitAccount(@Valid @RequestBody DebitAccountDTO request) {
+        commandsProducer.enqueue(new DebitAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
+    }
+
+    @PostMapping("/credit")
+    public ResponseEntity creditAccount(@Valid @RequestBody CreditAccountDTO request) {
+        commandsProducer.enqueue(new CreditAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
+    }
+
+    @PutMapping("/updateStatus")
+    public ResponseEntity updateStatus(@Valid @RequestBody UpdateAccountStatusDTO request) {
+        commandsProducer.enqueue(new UpdateAccountStatusCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAccountStatus()));
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/status/" + request.getAccountId()).build();
+    }
+
+
+    @GetMapping("allEvents")
+    public List<BaseEvent> getAllEvents() {
+        return eventStoreRepository.findAll();
+    }
+
+    @ExceptionHandler(Exception.class)
+    public String exceptionHandler(Exception exception) {
+        return exception.getMessage();
+    }
+
+
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/CommandsProducer.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/CommandsProducer.java
new file mode 100644
index 000000000..5f572e1c5
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/CommandsProducer.java
@@ -0,0 +1,21 @@
+package org.example.oracle.cqrs.command.producers;
+
+import org.example.oracle.cqrs.command.commands.BaseCommand;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CommandsProducer {
+    private final JmsTemplate jmsTemplate;
+    private final String queueName;
+
+    public CommandsProducer(JmsTemplate jmsTemplate, @Value("${txeventq.queue.commands.name}") String queueName) {
+        this.jmsTemplate = jmsTemplate;
+        this.queueName = queueName;
+    }
+
+    public void enqueue(BaseCommand command) {
+        jmsTemplate.convertAndSend(queueName, command);
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/EventProducer.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/EventProducer.java
new file mode 100644
index 000000000..cd85a0588
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/producers/EventProducer.java
@@ -0,0 +1,21 @@
+package org.example.oracle.cqrs.command.producers;
+
+import org.example.oracle.cqrs.common.events.BaseEvent;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EventProducer {
+    private final JmsTemplate jmsTemplate;
+    private final String queueName;
+
+    public EventProducer(JmsTemplate jmsTemplate, @Value("${txeventq.queue.events.name}") String queueName) {
+        this.jmsTemplate = jmsTemplate;
+        this.queueName = queueName;
+    }
+
+    public void enqueue(BaseEvent event) {
+        jmsTemplate.convertAndSend(queueName, event);
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/repository/EventStoreRepository.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/repository/EventStoreRepository.java
new file mode 100644
index 000000000..6d4b83471
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/repository/EventStoreRepository.java
@@ -0,0 +1,7 @@
+package org.example.oracle.cqrs.command.repository;
+
+import org.example.oracle.cqrs.common.events.BaseEvent;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface EventStoreRepository extends JpaRepository<BaseEvent, String> {
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreateAccountDTO.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreateAccountDTO.java
new file mode 100644
index 000000000..8ab009b5f
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreateAccountDTO.java
@@ -0,0 +1,16 @@
+package org.example.oracle.cqrs.common.Dtos;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.*;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateAccountDTO {
+    @Min(value = 0, message = "initialBalance can not be negative")
+    double initialBalance;
+    @NotBlank
+    String currency;
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreditAccountDTO.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreditAccountDTO.java
new file mode 100644
index 000000000..ed4ee396f
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/CreditAccountDTO.java
@@ -0,0 +1,19 @@
+package org.example.oracle.cqrs.common.Dtos;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreditAccountDTO {
+    @NotBlank
+    private String accountId;
+    @Min(value = 1, message = "amount can not be zero or negative")
+    private double amount;
+    @NotBlank
+    private String currency;
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/DebitAccountDTO.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/DebitAccountDTO.java
new file mode 100644
index 000000000..92ab5b889
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/DebitAccountDTO.java
@@ -0,0 +1,19 @@
+package org.example.oracle.cqrs.common.Dtos;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DebitAccountDTO {
+    @NotBlank
+    private String accountId;
+    @Min(value = 1, message = "amount can not be zero or negative")
+    private double amount;
+    @NotBlank
+    private String currency;
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/UpdateAccountStatusDTO.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/UpdateAccountStatusDTO.java
new file mode 100644
index 000000000..c8bf8906d
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/Dtos/UpdateAccountStatusDTO.java
@@ -0,0 +1,16 @@
+
+package org.example.oracle.cqrs.common.Dtos;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+
+@Data @AllArgsConstructor @NoArgsConstructor
+public class UpdateAccountStatusDTO {
+    @NotBlank
+    private String accountId ;
+    @NotBlank
+    private AccountStatus accountStatus ;
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/enums/AccountStatus.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/enums/AccountStatus.java
new file mode 100644
index 000000000..7271aa013
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/enums/AccountStatus.java
@@ -0,0 +1,5 @@
+package org.example.oracle.cqrs.common.enums;
+
+public enum AccountStatus{
+    CREATED, ACTIVATED, SUSPENDED, BLOCKED
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreatedEvent.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreatedEvent.java
new file mode 100644
index 000000000..b1aaadaf8
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreatedEvent.java
@@ -0,0 +1,31 @@
+package org.example.oracle.cqrs.common.events;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+
+
+@NoArgsConstructor
+@Entity
+public class AccountCreatedEvent extends BaseEvent {
+    @Getter
+    private double initialBalance;
+    @Getter
+    private String currency;
+    @Enumerated(EnumType.STRING)
+    @Getter
+    private AccountStatus accountStatus;
+    @Getter
+    private String accountId;
+
+    public AccountCreatedEvent(String id, double initialBalance, String currency, AccountStatus accountStatus, String accountId) {
+        super(id);
+        this.initialBalance = initialBalance;
+        this.currency = currency;
+        this.accountStatus = accountStatus;
+        this.accountId = accountId;
+    }
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreditedEvent.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreditedEvent.java
new file mode 100644
index 000000000..5c0082de9
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountCreditedEvent.java
@@ -0,0 +1,21 @@
+package org.example.oracle.cqrs.common.events;
+
+import jakarta.persistence.Entity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Entity
+@Getter
+public class AccountCreditedEvent extends BaseEvent {
+    private String currency;
+    private double amount;
+    private String accountId;
+
+    public AccountCreditedEvent(String id, String currency, double amount, String accountId) {
+        super(id);
+        this.currency = currency;
+        this.amount = amount;
+        this.accountId = accountId;
+    }
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountDebitedEvent.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountDebitedEvent.java
new file mode 100644
index 000000000..7b05de502
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountDebitedEvent.java
@@ -0,0 +1,23 @@
+package org.example.oracle.cqrs.common.events;
+
+import jakarta.persistence.Entity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Entity
+public class AccountDebitedEvent extends BaseEvent {
+    @Getter
+    private String accountId;
+    @Getter
+    private String currency;
+    @Getter
+    private double amount;
+
+    public AccountDebitedEvent(String id, String accountId, String currency, double amount) {
+        super(id);
+        this.accountId = accountId;
+        this.currency = currency;
+        this.amount = amount;
+    }
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountStatusUpdatedEvent.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountStatusUpdatedEvent.java
new file mode 100644
index 000000000..24c83aa8a
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/AccountStatusUpdatedEvent.java
@@ -0,0 +1,20 @@
+package org.example.oracle.cqrs.common.events;
+
+import jakarta.persistence.Entity;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+
+@NoArgsConstructor
+@Entity
+@Getter
+public class AccountStatusUpdatedEvent extends BaseEvent {
+    private AccountStatus newStatus;
+    private String accountId;
+
+    public AccountStatusUpdatedEvent(String id, AccountStatus newStatus, String accountId) {
+        super(id);
+        this.newStatus = newStatus;
+        this.accountId = accountId;
+    }
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/BaseEvent.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/BaseEvent.java
new file mode 100644
index 000000000..901d19fd8
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/common/events/BaseEvent.java
@@ -0,0 +1,19 @@
+package org.example.oracle.cqrs.common.events;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Inheritance;
+import jakarta.persistence.InheritanceType;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Inheritance(strategy = InheritanceType.JOINED)
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseEvent {
+    @Getter
+    @Id
+    private String id;
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/controller/AccountQueryRest.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/controller/AccountQueryRest.java
new file mode 100644
index 000000000..467afb9c2
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/controller/AccountQueryRest.java
@@ -0,0 +1,40 @@
+package org.example.oracle.cqrs.query.controller;
+
+
+import org.example.oracle.cqrs.query.entities.Account;
+import org.example.oracle.cqrs.query.repositories.AccountRepository;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/queries")
+public class AccountQueryRest {
+
+    private AccountRepository accountRepository;
+
+    public AccountQueryRest(AccountRepository accountRepository) {
+        this.accountRepository = accountRepository;
+    }
+
+    @GetMapping("allAccounts")
+    public List<Account> getAccounts() {
+        return accountRepository.findAll();
+    }
+
+    @GetMapping("/{accountId}")
+    public ResponseEntity<Account> getAccount(@PathVariable String accountId) {
+        return accountRepository.findById(accountId)
+                .map(ResponseEntity::ok)
+                .orElseGet(() -> ResponseEntity.notFound().build());
+    }
+
+    @GetMapping("status/{accountId}")
+    public ResponseEntity<String> getAccountStatus(@PathVariable String accountId) {
+        return accountRepository.findById(accountId)
+                .map(account -> ResponseEntity.ok(account.getStatus().toString()))
+                .orElseGet(() -> ResponseEntity.notFound().build());
+    }
+
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/entities/Account.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/entities/Account.java
new file mode 100644
index 000000000..fdd2912a2
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/entities/Account.java
@@ -0,0 +1,25 @@
+package org.example.oracle.cqrs.query.entities;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.Id;
+import lombok.*;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+
+import java.util.Date;
+
+@Data
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Account {
+    @Id
+    private String accountId;
+    private Date createdAt;
+    private double balance;
+    @Enumerated(EnumType.STRING)
+    private AccountStatus status;
+    private String currency;
+}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/eventHandlers/AccountEventHandlers.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/eventHandlers/AccountEventHandlers.java
new file mode 100644
index 000000000..5d0635783
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/eventHandlers/AccountEventHandlers.java
@@ -0,0 +1,72 @@
+package org.example.oracle.cqrs.query.eventHandlers;
+
+import org.example.oracle.cqrs.common.events.*;
+import org.example.oracle.cqrs.query.entities.Account;
+import org.example.oracle.cqrs.query.repositories.AccountRepository;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+@Component
+public class AccountEventHandlers {
+
+    private AccountRepository accountRepository;
+
+    public AccountEventHandlers(AccountRepository accountRepository) {
+        this.accountRepository = accountRepository;
+    }
+
+    @JmsListener(destination = "${txeventq.queue.events.name}", id = "sampleEvent")
+    void handleEvent(BaseEvent event) {
+
+        Account account;
+        switch (event) {
+            case AccountCreatedEvent accountCreatedEvent -> {
+
+                System.out.println("Handling AccountCreatedEvent: " + accountCreatedEvent);
+
+                account = Account.builder()
+                        .accountId(accountCreatedEvent.getAccountId())
+                        .balance(accountCreatedEvent.getInitialBalance())
+                        .createdAt(new Date())
+                        .currency(accountCreatedEvent.getCurrency())
+                        .status(accountCreatedEvent.getAccountStatus())
+                        .build();
+
+            }
+
+            case AccountDebitedEvent accountDebitedEvent -> {
+
+                System.out.println("Handling AccountDebitedEvent: " + accountDebitedEvent);
+
+                account = accountRepository.findById(accountDebitedEvent.getAccountId()).orElse(null);
+
+                /** to do  **/
+                //check the currency
+                //rollback the accountDebitedEvent if amount > balance
+                if (account.getBalance() < accountDebitedEvent.getAmount()) {
+                    throw new RuntimeException("Insufficient Balance");
+                }
+                account.setBalance(account.getBalance() - accountDebitedEvent.getAmount());
+
+            }
+
+            case AccountCreditedEvent accountCreditedEvent -> {
+                System.out.println("Handling accountCreditedEvent: " + accountCreditedEvent);
+                account = accountRepository.findById(accountCreditedEvent.getAccountId()).orElse(null);
+                account.setBalance(account.getBalance() + accountCreditedEvent.getAmount());
+            }
+
+            case AccountStatusUpdatedEvent accountStatusUpdatedEvent -> {
+                System.out.println("Handling accountStatusUpdatedEvent: " + accountStatusUpdatedEvent);
+                account = accountRepository.findById(accountStatusUpdatedEvent.getAccountId()).orElse(null);
+                account.setStatus(accountStatusUpdatedEvent.getNewStatus());
+            }
+            default -> throw new IllegalStateException("Unexpected value: " + event);
+        }
+
+        accountRepository.save(account);
+
+    }
+}
diff --git a/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/repositories/AccountRepository.java b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/repositories/AccountRepository.java
new file mode 100644
index 000000000..128431df8
--- /dev/null
+++ b/Oracle_CQRS/src/main/java/org/example/oracle/cqrs/query/repositories/AccountRepository.java
@@ -0,0 +1,6 @@
+package org.example.oracle.cqrs.query.repositories;
+
+import org.example.oracle.cqrs.query.entities.Account;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AccountRepository extends JpaRepository<Account, String> {}
diff --git a/Oracle_CQRS/src/main/resources/application.properties b/Oracle_CQRS/src/main/resources/application.properties
new file mode 100644
index 000000000..df301c7ce
--- /dev/null
+++ b/Oracle_CQRS/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+txeventq.queue.commands.name=${txeventq:commandsqueue}
+txeventq.queue.events.name=${txeventq:eventsqueue}
\ No newline at end of file
diff --git a/Oracle_CQRS/src/main/resources/application.yml b/Oracle_CQRS/src/main/resources/application.yml
new file mode 100644
index 000000000..8d2218036
--- /dev/null
+++ b/Oracle_CQRS/src/main/resources/application.yml
@@ -0,0 +1,26 @@
+springdoc:
+  swagger-ui:
+    path: /swagger-ui.html
+spring:
+  application:
+    name: Oracle_CQRS
+  jpa:
+    show-sql: true
+    hibernate:
+      ddl-auto: create-drop
+
+  datasource:
+    driver-class-name: oracle.jdbc.OracleDriver
+    type: oracle.ucp.jdbc.PoolDataSourceImpl
+    oracleucp:
+      initial-pool-size: 1
+      min-pool-size: 1
+      max-pool-size: 30
+      connection-pool-name: UCPSampleApplication
+      connection-factory-class-name: oracle.jdbc.pool.OracleDataSource
+    url: ${db_url:jdbc:oracle:thin:@//localhost:1521/FREEPDB1}
+    username: ${db_username:testuser}
+    password: ${db_password:password}
+server:
+  port: 8081
+
diff --git a/Oracle_CQRS/src/main/resources/init.sql b/Oracle_CQRS/src/main/resources/init.sql
new file mode 100644
index 000000000..bef3037ec
--- /dev/null
+++ b/Oracle_CQRS/src/main/resources/init.sql
@@ -0,0 +1,35 @@
+alter session set container = freepdb1;
+
+-- Configure testuser with the necessary privileges to use Transactional Event Queues.
+grant aq_user_role to testuser;
+grant execute on dbms_aq to testuser;
+grant execute on dbms_aqadm to testuser;
+grant execute ON dbms_aqin TO testuser;
+grant execute ON dbms_aqjms TO testuser;
+
+
+-- Create a Transactional Event Queue
+begin
+    -- See https://docs.oracle.com/en/database/oracle/oracle-database/23/arpls/DBMS_AQADM.html#GUID-93B0FF90-5045-4437-A9C4-B7541BEBE573
+    -- For documentation on creating Transactional Event Queues.
+    dbms_aqadm.create_transactional_event_queue(
+            queue_name         => 'testuser.commandsqueue',
+            queue_payload_type => DBMS_AQADM.JMS_TYPE,
+            multiple_consumers => false
+    );
+
+     dbms_aqadm.create_transactional_event_queue(
+            queue_name         => 'testuser.eventsqueue',
+            queue_payload_type => DBMS_AQADM.JMS_TYPE,
+            multiple_consumers => false
+    );
+
+    -- Start the queue
+    dbms_aqadm.start_queue(
+            queue_name         => 'testuser.commandsqueue'
+    );
+     dbms_aqadm.start_queue(
+            queue_name         => 'testuser.eventsqueue'
+    );
+end;
+/
\ No newline at end of file
diff --git a/Oracle_CQRS/src/test/java/org/example/oracle/cqrs/OracleCqrsApplicationTests.java b/Oracle_CQRS/src/test/java/org/example/oracle/cqrs/OracleCqrsApplicationTests.java
new file mode 100644
index 000000000..910d494d8
--- /dev/null
+++ b/Oracle_CQRS/src/test/java/org/example/oracle/cqrs/OracleCqrsApplicationTests.java
@@ -0,0 +1,13 @@
+package org.example.oracle.cqrs;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class OracleCqrsApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}


Reviews:
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:38:13Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:39:03Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:39:41Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:40:13Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:40:31Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:41:00Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:43:01Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:43:20Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:44:34Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:45:16Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:46:41Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:47:56Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:48:11Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:48:39Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:49:24Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:50:57Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:51:25Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:51:53Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:52:22Z
user: login: soulaimanGhailan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T22:06:12Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T13:42:40Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T13:44:20Z
user: login: anders-swanson
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T13:45:13Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:04:12Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:04:13Z
user: login: anders-swanson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T14:04:53Z

Review comments:
diff_hunk: @@ -0,0 +1,2 @@
+/mvnw text eol=lf
path: Oracle_CQRS/.gitattributes
user: login: anders-swanson
body: Any git files, can you add to the root project git files unless there is a compelling reason to have them separate?
created_at: 2025-05-08T00:38:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.example</groupId>
+    <artifactId>Oracle_CQRS</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Oracle_CQRS</name>
+    <description>Oracle_CQRS</description>
+    <url/>
path: Oracle_CQRS/pom.xml
user: login: anders-swanson
body: Either fill these tags or remove them, for samples you can use the [UPL](https://oss.oracle.com/licenses/upl/)
created_at: 2025-05-08T00:39:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.example</groupId>
+    <artifactId>Oracle_CQRS</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Oracle_CQRS</name>
+    <description>Oracle_CQRS</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc11</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <!--        <dependency>-->
path: Oracle_CQRS/pom.xml
user: login: anders-swanson
body: remove commented code
created_at: 2025-05-08T00:39:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.example</groupId>
+    <artifactId>Oracle_CQRS</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Oracle_CQRS</name>
+    <description>Oracle_CQRS</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc11</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <!--        <dependency>-->
+        <!--            <groupId>com.oracle.database.jdbc</groupId>-->
+        <!--            <artifactId>ucp</artifactId>-->
+        <!--            <scope>runtime</scope>-->
+        <!--        </dependency>-->
+
+
+        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.38</version>
path: Oracle_CQRS/pom.xml
user: login: anders-swanson
body: suggest using properties for versions
created_at: 2025-05-08T00:40:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.example</groupId>
+    <artifactId>Oracle_CQRS</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Oracle_CQRS</name>
+    <description>Oracle_CQRS</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc11</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <!--        <dependency>-->
+        <!--            <groupId>com.oracle.database.jdbc</groupId>-->
+        <!--            <artifactId>ucp</artifactId>-->
+        <!--            <scope>runtime</scope>-->
+        <!--        </dependency>-->
+
+
+        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.38</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.oracle.database.spring/oracle-spring-boot-starter-ucp -->
+        <dependency>
+            <groupId>com.oracle.database.spring</groupId>
+            <artifactId>oracle-spring-boot-starter-ucp</artifactId>
+            <version>25.1.0</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>2.8.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.oracle.database.spring</groupId>
+            <artifactId>oracle-spring-boot-starter-aqjms</artifactId>
+            <version>24.4.0</version>
path: Oracle_CQRS/pom.xml
user: login: anders-swanson
body: any reason you are using 24.4.0 instead of 25.1.0?
created_at: 2025-05-08T00:40:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,36 @@
+package org.example.oracle_cqrs;
+
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.JMSException;
+import oracle.jakarta.jms.AQjmsFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
+import org.springframework.jms.support.converter.MessageType;
+
+import javax.sql.DataSource;
+
+
+@SpringBootApplication
+public class OracleCqrsApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OracleCqrsApplication.class, args);
+    }
+
+
+    @Bean
+    public ConnectionFactory aqJmsConnectionFactory(DataSource ds) throws JMSException {
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/OracleCqrsApplication.java
user: login: anders-swanson
body: Can be removed in 25.1.0 starter for aqjms
created_at: 2025-05-08T00:41:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.5</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.example</groupId>
+    <artifactId>Oracle_CQRS</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Oracle_CQRS</name>
+    <description>Oracle_CQRS</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
path: Oracle_CQRS/pom.xml
user: login: anders-swanson
body: use java 21
created_at: 2025-05-08T00:43:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle_cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle_cqrs.command.commands.*;
+import org.example.oracle_cqrs.command.producers.EventProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.enums.AccountStatus;
+import org.example.oracle_cqrs.common.events.AccountCreatedEvent;
+import org.example.oracle_cqrs.common.events.AccountDebitedEvent;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        if (command instanceof CreateAccountCommand) {
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: suggest type switch for command types or function map
created_at: 2025-05-08T00:43:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle_cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle_cqrs.command.commands.*;
+import org.example.oracle_cqrs.command.producers.EventProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.enums.AccountStatus;
+import org.example.oracle_cqrs.common.events.AccountCreatedEvent;
+import org.example.oracle_cqrs.common.events.AccountDebitedEvent;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        if (command instanceof CreateAccountCommand) {
+
+            System.out.println("Handling create: " + command);
+            CreateAccountCommand createAccountCommand = (CreateAccountCommand) command;
+            if (createAccountCommand.getInitialBalance() < 0)
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: These type of errors (input validation) should be handled at the REST layer for the command service. That way the error is received as early as possible to short circuit unnecessary processing.
created_at: 2025-05-08T00:44:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle_cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle_cqrs.command.commands.*;
+import org.example.oracle_cqrs.command.producers.EventProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.enums.AccountStatus;
+import org.example.oracle_cqrs.common.events.AccountCreatedEvent;
+import org.example.oracle_cqrs.common.events.AccountDebitedEvent;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        if (command instanceof CreateAccountCommand) {
+
+            System.out.println("Handling create: " + command);
+            CreateAccountCommand createAccountCommand = (CreateAccountCommand) command;
+            if (createAccountCommand.getInitialBalance() < 0)
+                throw new IllegalArgumentException("Initial balance is negative");
+
+            AccountCreatedEvent event = new AccountCreatedEvent(UUID.randomUUID().toString()
+                    , createAccountCommand.getInitialBalance(),
+                    createAccountCommand.getCurrency(),
+                    AccountStatus.CREATED);
+
+            eventProducer.enqueue(event);
+            eventStoreRepository.save(event);
+
+
+        } else if (command instanceof DebitAccountCommand) {
+
+            System.out.println("Handling debit: " + command);
+            DebitAccountCommand debitAccountCommand = (DebitAccountCommand) command;
+            if (debitAccountCommand.getAmount() < 0)
+                throw new IllegalArgumentException("Amount is negative");
+
+            AccountDebitedEvent event = new AccountDebitedEvent(UUID.randomUUID().toString(),
+                    debitAccountCommand.getAccountId()
+                    , debitAccountCommand.getCurrency(),
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: style: comma is on new line. Optionally, check out Maven Checkstyle for a linter
created_at: 2025-05-08T00:45:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle_cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle_cqrs.command.commands.*;
+import org.example.oracle_cqrs.command.producers.EventProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.enums.AccountStatus;
+import org.example.oracle_cqrs.common.events.AccountCreatedEvent;
+import org.example.oracle_cqrs.common.events.AccountDebitedEvent;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        if (command instanceof CreateAccountCommand) {
+
+            System.out.println("Handling create: " + command);
+            CreateAccountCommand createAccountCommand = (CreateAccountCommand) command;
+            if (createAccountCommand.getInitialBalance() < 0)
+                throw new IllegalArgumentException("Initial balance is negative");
+
+            AccountCreatedEvent event = new AccountCreatedEvent(UUID.randomUUID().toString()
+                    , createAccountCommand.getInitialBalance(),
+                    createAccountCommand.getCurrency(),
+                    AccountStatus.CREATED);
+
+            eventProducer.enqueue(event);
+            eventStoreRepository.save(event);
+
+
+        } else if (command instanceof DebitAccountCommand) {
+
+            System.out.println("Handling debit: " + command);
+            DebitAccountCommand debitAccountCommand = (DebitAccountCommand) command;
+            if (debitAccountCommand.getAmount() < 0)
+                throw new IllegalArgumentException("Amount is negative");
+
+            AccountDebitedEvent event = new AccountDebitedEvent(UUID.randomUUID().toString(),
+                    debitAccountCommand.getAccountId()
+                    , debitAccountCommand.getCurrency(),
+                    debitAccountCommand.getAmount());
+
+            eventProducer.enqueue(event);
+            eventStoreRepository.save(event);
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: this looks like it will be in every path? can be moved out of the if block
created_at: 2025-05-08T00:46:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+package org.example.oracle_cqrs.command.commands;
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/commands/CreditAccountCommand.java
user: login: anders-swanson
body: package should be org.example.oracle.cqrs.command.commands;
created_at: 2025-05-08T00:47:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,18 @@
+package org.example.oracle_cqrs.command.commands;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.example.oracle_cqrs.common.enums.AccountStatus;
+
+@Getter
+@NoArgsConstructor
+public class UpdateAccountStatusCommand extends BaseCommand<String> {
+    private String accountId;
+    private AccountStatus accountStatus;
+
+    public UpdateAccountStatusCommand(String id, String accountId ,AccountStatus accountStatus) {
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/commands/UpdateAccountStatusCommand.java
user: login: anders-swanson
body: spacing on comma
created_at: 2025-05-08T00:48:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,68 @@
+package org.example.oracle_cqrs.command.controlles;
+
+
+import org.example.oracle_cqrs.command.commands.CreateAccountCommand;
+import org.example.oracle_cqrs.command.commands.CreditAccountCommand;
+import org.example.oracle_cqrs.command.commands.DebitAccountCommand;
+import org.example.oracle_cqrs.command.commands.UpdateAccountStatusCommand;
+import org.example.oracle_cqrs.command.producers.CommandsProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.Dtos.CreateAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.CreditAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.DebitAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.UpdateAccountStatusDTO;
+import org.example.oracle_cqrs.common.events.BaseEvent;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/commands")
+public class AccountCommandRest {
+
+    private CommandsProducer commandsProducer ;
+    private EventStoreRepository eventStoreRepository;
+
+    AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
+        this.commandsProducer = commandsProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+    @PostMapping("/create")
+    public void createAccount(@RequestBody CreateAccountDTO request){
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/controlles/AccountCommandRest.java
user: login: anders-swanson
body: these are where command validation can occur, before we try to place on the queue
created_at: 2025-05-08T00:48:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,68 @@
+package org.example.oracle_cqrs.command.controlles;
+
+
+import org.example.oracle_cqrs.command.commands.CreateAccountCommand;
+import org.example.oracle_cqrs.command.commands.CreditAccountCommand;
+import org.example.oracle_cqrs.command.commands.DebitAccountCommand;
+import org.example.oracle_cqrs.command.commands.UpdateAccountStatusCommand;
+import org.example.oracle_cqrs.command.producers.CommandsProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.Dtos.CreateAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.CreditAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.DebitAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.UpdateAccountStatusDTO;
+import org.example.oracle_cqrs.common.events.BaseEvent;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/commands")
+public class AccountCommandRest {
+
+    private CommandsProducer commandsProducer ;
+    private EventStoreRepository eventStoreRepository;
+
+    AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
+        this.commandsProducer = commandsProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+    @PostMapping("/create")
+    public void createAccount(@RequestBody CreateAccountDTO request){
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/controlles/AccountCommandRest.java
user: login: anders-swanson
body: suggest also to return 201 CREATED instead of void
created_at: 2025-05-08T00:49:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,36 @@
+package org.example.oracle_cqrs;
+
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.JMSException;
+import oracle.jakarta.jms.AQjmsFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
+import org.springframework.jms.support.converter.MessageType;
+
+import javax.sql.DataSource;
+
+
+@SpringBootApplication
+public class OracleCqrsApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OracleCqrsApplication.class, args);
+    }
+
+
+    @Bean
+    public ConnectionFactory aqJmsConnectionFactory(DataSource ds) throws JMSException {
+        return AQjmsFactory.getConnectionFactory(ds);
+    }
+
+
+    @Bean
+    public MappingJackson2MessageConverter jacksonJmsMessageConverter() {
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/OracleCqrsApplication.java
user: login: anders-swanson
body: Something for us to look into (I may do this): How can we make a JMS message converter that uses OSON (Oracle binary JSON format)?
created_at: 2025-05-08T00:50:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+package org.example.oracle_cqrs.query.eventHandlers;
+
+import org.example.oracle_cqrs.common.events.*;
+import org.example.oracle_cqrs.query.entities.Account;
+import org.example.oracle_cqrs.query.repositories.AccountRepository;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.UUID;
+
+@Component
+public class AccountEventHandlers {
+
+    private AccountRepository accountRepository;
+
+    public AccountEventHandlers(AccountRepository accountRepository) {
+        this.accountRepository = accountRepository;
+    }
+
+    @JmsListener(destination = "${txeventq.queue.events.name}", id = "sampleEvent")
+    void handleEvent(BaseEvent event) {
+        if (event instanceof AccountCreatedEvent) {
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/query/eventHandlers/AccountEventHandlers.java
user: login: anders-swanson
body: suggest type switch (j21)
created_at: 2025-05-08T00:51:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,26 @@
+springdoc:
+  swagger-ui:
+    path: /swagger-ui.html
path: Oracle_CQRS/src/main/resources/application.yml
user: login: anders-swanson
body: this is really nice!
created_at: 2025-05-08T00:51:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,36 @@
+alter session set container = freepdb1;
+-- ALTER USER testuser QUOTA UNLIMITED ON USERS;
+
+-- Configure testuser with the necessary privileges to use Transactional Event Queues.
+grant aq_user_role to testuser;
+grant execute on dbms_aq to testuser;
+grant execute on dbms_aqadm to testuser;
+grant execute ON dbms_aqin TO testuser;
+grant execute ON dbms_aqjms TO testuser;
+-- grant execute on dbms_teqk to testuser;
path: Oracle_CQRS/src/main/resources/init.sql
user: login: anders-swanson
body: remove commented line
created_at: 2025-05-08T00:52:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,68 @@
+package org.example.oracle_cqrs.command.controlles;
+
+
+import org.example.oracle_cqrs.command.commands.CreateAccountCommand;
+import org.example.oracle_cqrs.command.commands.CreditAccountCommand;
+import org.example.oracle_cqrs.command.commands.DebitAccountCommand;
+import org.example.oracle_cqrs.command.commands.UpdateAccountStatusCommand;
+import org.example.oracle_cqrs.command.producers.CommandsProducer;
+import org.example.oracle_cqrs.command.repository.EventStoreRepository;
+import org.example.oracle_cqrs.common.Dtos.CreateAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.CreditAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.DebitAccountDTO;
+import org.example.oracle_cqrs.common.Dtos.UpdateAccountStatusDTO;
+import org.example.oracle_cqrs.common.events.BaseEvent;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/commands")
+public class AccountCommandRest {
+
+    private CommandsProducer commandsProducer ;
+    private EventStoreRepository eventStoreRepository;
+
+    AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
+        this.commandsProducer = commandsProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+    @PostMapping("/create")
+    public void createAccount(@RequestBody CreateAccountDTO request){
path: Oracle_CQRS/src/main/java/org/example/oracle_cqrs/command/controlles/AccountCommandRest.java
user: login: soulaimanGhailan
body: as you know we’re using a bus abstraction between the API and business layer via Oracle TxEventQ, we have two response options at the rest layer: ether we could return a ResponseEntity says that command has been created and valid as you suggested, or we could explore CompletableFuture to wait for the result by fetching it from the TxEventQ once the event has been processed 
created_at: 2025-05-08T22:06:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle.cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle.cqrs.command.commands.*;
+import org.example.oracle.cqrs.common.events.*;
+import org.example.oracle.cqrs.command.producers.EventProducer;
+import org.example.oracle.cqrs.command.repository.EventStoreRepository;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        BaseEvent event;
+
+        switch (command) {
+            case CreateAccountCommand createAccountCommand -> {
path: Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: I'm fine with the way this is implemented, I just want to suggest a simpler way: if you add a method `createEvent` to the BaseCommand interface, then each type of command can implement it, including the exception handling.
created_at: 2025-05-16T13:42:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,75 @@
+package org.example.oracle.cqrs.command.controlles;
+
+
+import jakarta.validation.Valid;
+import org.example.oracle.cqrs.command.commands.CreateAccountCommand;
+import org.example.oracle.cqrs.command.commands.CreditAccountCommand;
+import org.example.oracle.cqrs.command.commands.DebitAccountCommand;
+import org.example.oracle.cqrs.command.commands.UpdateAccountStatusCommand;
+import org.example.oracle.cqrs.command.producers.CommandsProducer;
+import org.example.oracle.cqrs.command.repository.EventStoreRepository;
+import org.example.oracle.cqrs.common.Dtos.CreateAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.CreditAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.DebitAccountDTO;
+import org.example.oracle.cqrs.common.Dtos.UpdateAccountStatusDTO;
+import org.example.oracle.cqrs.common.events.BaseEvent;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/commands")
+public class AccountCommandRest {
+
+    private CommandsProducer commandsProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    AccountCommandRest(CommandsProducer commandsProducer, EventStoreRepository eventStoreRepository) {
+        this.commandsProducer = commandsProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+    @PostMapping("/create")
+    public ResponseEntity createAccount(@Valid @RequestBody CreateAccountDTO request) {
+        String accountId = UUID.randomUUID().toString();
+        commandsProducer.enqueue(new CreateAccountCommand(UUID.randomUUID().toString(), request.getInitialBalance(), request.getCurrency(), accountId));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/status/" + accountId).build();
+    }
+
+    @PostMapping("/debit")
+    public ResponseEntity debitAccount(@Valid @RequestBody DebitAccountDTO request) {
+        commandsProducer.enqueue(new DebitAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
+    }
+
+    @PostMapping("/credit")
+    public ResponseEntity creditAccount(@Valid @RequestBody CreditAccountDTO request) {
+        commandsProducer.enqueue(new CreditAccountCommand(UUID.randomUUID().toString(), request.getAccountId(), request.getAmount(), request.getCurrency()));
+
+        return ResponseEntity.status(HttpStatus.ACCEPTED).header("Location", "/api/queries/" + request.getAccountId()).build();
path: Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/controlles/AccountCommandRest.java
user: login: anders-swanson
body: fyi, There's a LOCATION const in the HttpHeaders spring class: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpHeaders.html#LOCATION
created_at: 2025-05-16T13:44:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,13 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+
+@AllArgsConstructor @NoArgsConstructor
+public class BaseCommand<T>{
+    @Getter
+    private T id;
+
path: Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/BaseCommand.java
user: login: anders-swanson
body: public abstract BaseEvent createEvent() {}
created_at: 2025-05-16T14:04:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+package org.example.oracle.cqrs.command.commands;
+
+import lombok.*;
+
+@Getter @NoArgsConstructor
+public class CreateAccountCommand extends BaseCommand<String> {
+    private double initialBalance;
+    private String currency;
+    private String accountId ;
+
+    public CreateAccountCommand(String id, double initialBalance, String currency, String accountId) {
+        super(id);
+        this.initialBalance = initialBalance;
+        this.currency = currency;
+        this.accountId = accountId;
+    }
path: Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/commands/CreateAccountCommand.java
user: login: anders-swanson
body: public BaseEvent createEvent() {

                System.out.println("Handling create: " + command);
                if (createAccountCommand.getInitialBalance() < 0)
                    throw new IllegalArgumentException("Initial balance is negative");

                event = new AccountCreatedEvent(UUID.randomUUID().toString(), createAccountCommand.getInitialBalance(), createAccountCommand.getCurrency(), AccountStatus.CREATED, createAccountCommand.getAccountId());
}
created_at: 2025-05-16T14:04:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+package org.example.oracle.cqrs.command.aggregates;
+
+import jakarta.transaction.Transactional;
+import lombok.*;
+
+import org.example.oracle.cqrs.command.commands.*;
+import org.example.oracle.cqrs.common.events.*;
+import org.example.oracle.cqrs.command.producers.EventProducer;
+import org.example.oracle.cqrs.command.repository.EventStoreRepository;
+import org.example.oracle.cqrs.common.enums.AccountStatus;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+
+@Component
+@Transactional
+public class AccountAggregate {
+    @Getter
+    private String accountId;
+    @Getter
+    private double currentBalance;
+    @Getter
+    private String currency;
+    @Getter
+    private AccountStatus status;
+
+    private EventProducer eventProducer;
+    private EventStoreRepository eventStoreRepository;
+
+    public AccountAggregate(EventProducer eventProducer, EventStoreRepository eventStoreRepository) {
+        this.eventProducer = eventProducer;
+        this.eventStoreRepository = eventStoreRepository;
+    }
+
+
+    @JmsListener(destination = "${txeventq.queue.commands.name}", id = "sampleCommand")
+    void handleCommand(BaseCommand command) {
+
+        BaseEvent event;
+
+        switch (command) {
+            case CreateAccountCommand createAccountCommand -> {
+                System.out.println("Handling create: " + command);
+                if (createAccountCommand.getInitialBalance() < 0)
+                    throw new IllegalArgumentException("Initial balance is negative");
+
+                event = new AccountCreatedEvent(UUID.randomUUID().toString(), createAccountCommand.getInitialBalance(), createAccountCommand.getCurrency(), AccountStatus.CREATED, createAccountCommand.getAccountId());
+
+            }
+            case DebitAccountCommand debitAccountCommand -> {
+                System.out.println("Handling debit: " + command);
+                if (debitAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
+
+                event = new AccountDebitedEvent(UUID.randomUUID().toString(), debitAccountCommand.getAccountId(), debitAccountCommand.getCurrency(), debitAccountCommand.getAmount());
+
+
+            }
+            case CreditAccountCommand creditAccountCommand -> {
+                System.out.println("Handling debit: " + command);
+                if (creditAccountCommand.getAmount() < 0) throw new IllegalArgumentException("Amount is negative");
+
+                event = new AccountCreditedEvent(UUID.randomUUID().toString(), creditAccountCommand.getCurrency(), creditAccountCommand.getAmount(), creditAccountCommand.getAccountId());
+
+
+            }
+            case UpdateAccountStatusCommand updateAccountStatusCommand -> {
+                System.out.println("Handling debit: " + command);
+                event = new AccountStatusUpdatedEvent(UUID.randomUUID().toString(), updateAccountStatusCommand.getAccountStatus(), updateAccountStatusCommand.getAccountId());
+
+            }
+
+            default -> throw new IllegalStateException("Unexpected value: " + command);
+        }
+
path: Oracle_CQRS/src/main/java/org/example/oracle/cqrs/command/aggregates/AccountAggregate.java
user: login: anders-swanson
body: BaseEvent event = baseCommand.createEvent();
created_at: 2025-05-16T14:04:53Z
author_association: MEMBER

PR comments:
user: login: soulaimanGhailan
created_at: 2025-05-08T21:36:37Z
body: Thanks for the review, I’ll make the suggested changes and push an update shortly.
author_association: MEMBER
user: login: oracle-contributor-agreement[bot]
created_at: 2025-05-11T00:09:25Z
body: Thank you for your pull request and welcome to our community! To contribute, please sign the Oracle Contributor Agreement (OCA).
The following contributors of this PR have not signed the OCA:

- sghailan@192.168.1.10 

To sign the OCA, please [create an Oracle account](https://profile.oracle.com/myprofile/account/create-account.jspx) and sign the OCA in [Oracle's Contributor Agreement Application](https://oca.opensource.oracle.com/).

When signing the OCA, please provide your GitHub username. After signing the OCA and getting an OCA approval from Oracle, this PR will be automatically updated.

*If you are an Oracle employee, please make sure that you are a member of the main [Oracle GitHub organization](https://github.com/oracle), and your [membership in this organization is public](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/publicizing-or-hiding-organization-membership).*

author_association: NONE
user: login: soulaimanGhailan
created_at: 2025-05-11T00:28:22Z
body: hello Anders, could you please review again and tell me if there is anything needs an update
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-05-13T19:07:57Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=oracle_microservices-datadriven&pullRequest=1059) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=oracle_microservices-datadriven&pullRequest=1059&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=oracle_microservices-datadriven&pullRequest=1059&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=oracle_microservices-datadriven&pullRequest=1059&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=oracle_microservices-datadriven&pullRequest=1059&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=oracle_microservices-datadriven&pullRequest=1059&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=oracle_microservices-datadriven&pullRequest=1059)


author_association: NONE