This project explains how to use CQRS and event sourcing in general, as well as how to use Axon with Spring Boot in the microservices space.
the Axon Framework, a Java framework for building scalable and high-performance event-driven applications.
.
├── main
│ ├── java
│ │ └── dev
│ │ └── elma
│ │ └── demo
│ │ ├── DemoApplication.java
│ │ ├── commands
│ │ │ ├── aggregates
│ │ │ │ └── AccountAggregate.java
│ │ │ └── controllers
│ │ │ └── AccountCommandController.java
│ │ ├── commonapi
│ │ │ ├── commands
│ │ │ │ ├── BaseCommand.java
│ │ │ │ ├── CreateAccountCommand.java
│ │ │ │ ├── CreditAccountCommand.java
│ │ │ │ └── DebitAccountCommand.java
│ │ │ ├── dtos
│ │ │ │ ├── CreateAccountCommandDTO.java
│ │ │ │ ├── CreditAccountCommandDTO.java
│ │ │ │ └── DebitAccountCommandDTO.java
│ │ │ ├── enums
│ │ │ │ ├── AccountStatus.java
│ │ │ │ └── TransactionType.java
│ │ │ ├── events
│ │ │ │ ├── AccountCreatedEvent.java
│ │ │ │ ├── AccountCreditedEvent.java
│ │ │ │ ├── AccountDebitedEvent.java
│ │ │ │ └── BaseEvent.java
│ │ │ └── exceptions
│ │ │ └── AccountCommandExceptions.java
│ │ └── queries
│ │ ├── controllers
│ │ │ └── QueriesController.java
│ │ ├── entities
│ │ │ ├── Account.java
│ │ │ └── AccountTransaction.java
│ │ ├── query
│ │ │ ├── GetAllAcount.java
│ │ │ ├── GetOne.java
│ │ │ └── GetTransactionOfAccount.java
│ │ ├── repositories
│ │ │ ├── AccountRepository.java
│ │ │ └── AccountTransactionRepository.java
│ │ └── services
│ │ └── AccountEventHandlerServices.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
Axon | Swagger | Other |
---|---|---|


Commands are a fundamental concept. They are messages that represent requests to change the state of an aggregate (a domain object).
There are 3 commands in our application:
* Create Account Command
* Credit Amount to Account Command
* Debit Amount from Account Command
Also We have Base Command as an abstarct Class
The @TargetAggregateIdentifier
annotation is often used in command handling methods within an aggregate to specify which field within the aggregate represents the target aggregate identifier. This is important for Axon to route commands to the correct aggregate instance.
@AllArgsConstructor
public abstract class BaseCommand<T> {
@TargetAggregateIdentifier
@Getter
private T id;
}
@Getter
public class CreateAccountCommand extends BaseCommand<String>{
private BigDecimal balance;
private String currency;
public CreateAccountCommand(String id,BigDecimal balance,String currency) {
super(id);
this.balance=balance;
this.currency=currency;
}
}
@Getter
public class CreditAccountCommand extends BaseCommand<String>{
private BigDecimal amount;
private String currency;
public CreditAccountCommand(String accountId, BigDecimal amount,String currency) {
super(accountId);
this.amount=amount;
this.currency=currency;
}
}
@Getter
public class DebitAccountCommand extends BaseCommand<String>{
private BigDecimal amount;
private String currency;
public DebitAccountCommand(String accountId, BigDecimal amount, String currency) {
super(accountId);
this.amount=amount;
this.currency=currency;
}
}
Events are closely related to commands, as mentioned in the previous response. While commands represent requests to perform actions, events represent the results or consequences of those actions.
There are 3 events in our application:
* Create Account event
* Credit Amount to Account event
* Debit Amount from Account event
Also We have Base Event as an abstarct Class
public abstract class BaseEvent<T> {
@Getter private T id;
public BaseEvent(T id) {
this.id = id;
}
}
@Getter
public class AccountCreatedEvent extends BaseEvent<String>{
private BigDecimal balance;
private String currency;
private AccountStatus status;
public AccountCreatedEvent(String id, BigDecimal balance, String currency, AccountStatus status){
super(id);
this.balance=balance;
this.currency=currency;
this.status=status;
}
}
@Getter
public class AccountCreditedEvent extends BaseEvent<String>{
private BigDecimal amount;
private String currency;
public AccountCreditedEvent(String id, BigDecimal amount,String currency){
super(id);
this.amount=amount;
this.currency=currency;
}
}
@Getter
public class AccountDebitedEvent extends BaseEvent<String>{
private BigDecimal amount;
private String currency;
public AccountDebitedEvent(String id, BigDecimal amount,String currency){
super(id);
this.amount=amount;
this.currency=currency;
}
}
aggregate is a fundamental concept that represents a cluster of related domain objects and encapsulates the business logic and state for a specific part of the domain.
@Aggregate //axonAggregate
public class AccountAggregate {
@AggregateIdentifier
private String id;
private BigDecimal balance;
private String currency;
private AccountStatus status;
protected AccountAggregate() {
//Required By Axon
}
@CommandHandler
public AccountAggregate(CreateAccountCommand command) {
//Decision Functions Here
if(command.getBalance().doubleValue()<=0) throw new AccountCommandExceptions("Negative Balance");
// When all commands are acceptable then we transfer them to be events and save it in events store
AccountCreatedEvent event = new AccountCreatedEvent(command.getId(), command.getBalance(), command.getCurrency(), AccountStatus.CREATED);
AggregateLifecycle.apply(event); // publish events
}
//Now we create an event handler whose update the state of our account (application)
@EventSourcingHandler
public void on(AccountCreatedEvent event){
this.id=event.getId();
this.balance=event.getBalance();
this.currency=event.getCurrency();
this.status=event.getStatus();
}
@CommandHandler
public void handle(DebitAccountCommand command){
if(this.balance.doubleValue()<command.getAmount().doubleValue()) throw new AccountCommandExceptions("Balance insufficient");
AccountDebitedEvent accountDebitedEvent = new AccountDebitedEvent(command.getId().toString(), command.getAmount(), command.getCurrency());
AggregateLifecycle.apply(accountDebitedEvent);
}
@EventSourcingHandler
public void on(AccountDebitedEvent event){
this.balance=BigDecimal.valueOf(this.balance.doubleValue()-event.getAmount().doubleValue());
}
@CommandHandler
public void handle(CreditAccountCommand command){
if(command.getAmount().doubleValue()<=0) throw new AccountCommandExceptions("Negative Amount");
AggregateLifecycle.apply(
new AccountCreditedEvent(
command.getId(),
command.getAmount(),
command.getCurrency()
)
);
}
@EventSourcingHandler
public void on(AccountCreditedEvent event){
this.balance=BigDecimal.valueOf(this.balance.doubleValue()+event.getAmount().doubleValue());
}
}