In Exploring the Commands, Command Bus and Command Handler, we looked at the command handling aspect of CQRS using Axon, where we had commands created, that were dispatched into a command bus via a command gateway which were then subsequently handled by their respective command handlers.
But in that post, all what the command handlers did was to print to the console. In real life applications, you would want the commands to lead to some business logic being executed, which might lead to state change in the application.
Achieving that, is what we look into in this post.
Following with the sample application
The exploringCQRSwithAxon is a project on Github with a simple application meant to illustrate the topics covered in the series of post on exploring CQRS with Axon.
To follow this post along with the accompanying project,
first clone the repo:
git clone git@github.com:dadepo/exploringCQRSwithAxon.git
then check out to the appropriate commit hash to have the project in a state that illustrates the topics covered in this post:
git checkout bccb3bbc796a7ec9c0b3747da3f2650ff03d09b4.
Objective of this post
The goal of this post is to extend the sample application so that it’s command handling component can lead to application of business logic and state change. This will see us adding a repository and building out the entity.
Basically the section of the CQRS diagram shown below:
At the end of this post, we would be able to debit/credit our dummy accounts (state changes) and we would be able to view their balance in the browser.
In other to achieve our objective, some changes has been made to the code. I now go through the main changes.
Account class added.
@Entity public class Account extends AbstractAggregateRoot { @Id private String id; @Column private Double balance; public Account() { } public Account(String id) { this.id = id; this.balance = 0.0d; } /** * Business Logic * Cannot debit with a negative amount * Cannot debit with more than a million amount (You laundering money?) * @param debitAmount */ public void debit(Double debitAmount) { if (Double.compare(debitAmount, 0.0d) > 0 && this.balance - debitAmount > -1) { this.balance -= debitAmount; } else { throw new IllegalArgumentException("Cannot debit with the amount"); } } /** * Business Logic * Cannot credit with a negative amount * Cannot credit with amount that leaves the account * balance in a negative state * @param creditAmount */ public void credit(Double creditAmount) { if (Double.compare(creditAmount, 0.0d) > 0 && Double.compare(creditAmount, 1000000) < 0) { this.balance += creditAmount; } else { throw new IllegalArgumentException("Cannot credit with the amount"); } } public Double getBalance() { return balance; } public void setIdentifier(String id) { this.id = id; } @Override public Object getIdentifier() { return id; } }
This is our Entity. And it is the Entity whose state we would be persisting. We also have both the credit and debit methods, which both implement the credit and debit business logic, as part of the Entity.
You will also notice two things. We are using @Entity annotation and our class is extending an Axon class the: AbstractAggregateRoot.
We have the @Entity annotation because we have chosen to model our entity as JPA entities, meaning that the persistence layer will be JPA.
We have the Account class extends AbstractAggregateRoot because the Account class has been designated as the Aggregate Root. As defined in the previous post, an aggregate is a collection of objects and its Aggregate Root, Aggregates can be exactly one object, which in that case, the object will be the Aggregate Root: the Account is the only object in our aggregate, and thus the Aggregate Root.
So why did the Account class extend AbstractAggregateRoot?
This is because, even though the Account has been annotated as a JPA entity, we won’t be directly using JPA’s entitymanager to interact with it. Our interaction would be done using Axon’s repository infrastructure, and Axon’s repository infrastructure requires that the aggregate root be of type AggregateRoot which is exactly what we achieved by having it extend AbstractAggregateRoot since it is an implementation of AggregateRoot.
It is worth noting that Axon provides a couple of implementation of the AggregateRoot. AbstractAggregateRoot is just one of them and it works fine for a JPA backed Entity.
The next thing we need to do is look at the repository that allows us to be able to retrieve and persist the Account entity.
Adding the repository
Normally if we were not using the Axon Framework, we could build our repository by using the EntityManager directly (or use something like Spring JPA Data). But since we are using the infrastructure provided by Axon, we used Axon’s implementation of a JPA backed repository: the GenericJpaRepository.
The GenericJpaRepository, needs to be configured with an EntityManager. This is done through the SimpleEntityManagerProvider. The addition to the configuration shows exactly how we wired up a GenericJpaRepository and configured it with an EntityManager.
/** * Configures a GenericJpaRepository as a Spring Bean. The Repository would * be used to access * the Account entity. * */ @Bean public GenericJpaRepository genericJpaRepository() { SimpleEntityManagerProvider entityManagerProvider = new SimpleEntityManagerProvider(entityManager); return new GenericJpaRepository(entityManagerProvider, Account.class); }
Note: We also updated our initialization code to add two Accounts entity for us on startup.
@Component public class Db { @Autowired @Qualifier("transactionManager") protected PlatformTransactionManager txManager; @Autowired private Repository repository; @PostConstruct private void init(){ TransactionTemplate transactionTmp = new TransactionTemplate(txManager); transactionTmp.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { UnitOfWork uow = DefaultUnitOfWork.startAndGet(); repository.add(new Account("acc-one")); repository.add(new Account("acc-two")); uow.commit(); } }); } }
We would also be using an in memory database, so we added the h2 in memory database to the dependency:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
The Spring Boot application.properties is also updated to contain the required settings:
# Datasource configuration spring.datasource.url=jdbc:h2:mem:exploredb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.datasource.validation-query=SELECT 1; spring.datasource.initial-size=2 spring.datasource.sql-script-encoding=UTF-8 spring.jpa.database=h2 spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create spring.freemarker.cache=false
With all these setup, we are ready to introduce our repository into our command handler and instead of having the command handler print stuff into the console as they were doing in Exploring CQRS with Axon Framework: The Command, Command Bus and Command Handling Components, we can now have them do something useful, like retrieving an account from the repository, change it state and store it back.
For example our CreditAccountHandler now look like this:
@Component public class CreditAccountHandler { @Autowired private Repository repository; @CommandHandler public void handle(CreditAccountCommand creditAccountCommandCommand){ Account accountToCredit = (Account) repository .load(creditAccountCommandCommand.getAccount()); accountToCredit.credit(creditAccountCommandCommand.getAmount()); } }
and the DebitAccountHolder look like this:
@Component public class DebitAccountHandler { @Autowired private Repository repository; @CommandHandler public void handle(DebitAccountCommand debitAccountCommandCommand){ Account accountToDebit = (Account) repository .load(debitAccountCommandCommand.getAccount()); accountToDebit.debit(debitAccountCommandCommand.getAmount()); } }
Where we injected the Repository (the GenericJpaRepository) since this is the only implementation we have provided in the context.
We then used this repository to load the account entity, call the debit() or credit() method that applies the business logic that changes the state.
Note that once we retrieved the Account from the repository, we did not have to explicitly call a save, or persist method in other to have the changes we have made be persisted. The Command Handling infrastructure works with the Axon repository to ensure persistence of the entity is done automatically once the Command Handling method returns.
If you examine the logic in the handler method, you realize what it is doing is simply:
- Retrieve the Aggregate Root from the repository
- Call the required method on the object saved.
In such a scenario you will not need to have the command handler as an obvious separate component, which is why that method was not used in this explanation. But in real life application, using the @CommandHandler directly on the Aggregate Root should be the recommended approach.
The remaining update to the code is made to the view layer.
We added another controller: ViewController which, this time around, makes direct use of the EntityManager to retrieve the state of the accounts meant for display, which is ok, as we are using it for the view:
@Controller public class ViewController { @Autowired private EntityManager entityManager; @RequestMapping(value = "/view", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<Map<String, Double>> getAccounts(@RequestBody List<String> accountsNumber) { List<Map<String, Double>> accounts = new ArrayList<>(); accountsNumber.stream().forEach(no -> { Account account = entityManager.find(Account.class, no); Map<String, Double> accountMap = new HashMap<>(); accountMap.put((String) account.getIdentifier(), account.getBalance()); accounts.add(accountMap); }); return accounts; } }
Then we updated the JavaScript on the client side to poll the /view url to get the state of the two accounts:
$.ajax({ url: "/view", method: "POST", contentType: "application/json", data: JSON.stringify(["acc-one", "acc-two"]), success: function(accounts) { var html = ""; accounts.forEach(function(account){ for (var key in account) { html += "<tr><td>" + key + "</td><td>" + account[key] + "</td></tr>" } }); $("table#balance tbody").html(html); }, error: function(a) { console.log(a); } }); }, 2000);
If you start up the application you will have something similar to the screenshot below:
Now when you debit or credit any of the accounts you see this reflected almost immediately in the browser.
Overview of the Axon Building Blocks
AggregateRoot
This is the interface aggregates in Axon implements. Implementing this interface makes it possible for Axon’s repository to perform it’s task of retrieving, storing and publishing of domain events etc.
AbstractAggregateRoot
Most of (if not all) of Axon’s core Interfaces that serves as its building blocks comes with implementations that should cater for the general use case and should get you started quickly. AbstractAggregateRoot is such an implementation. It is an implementation of the AggregateRoot interface. AbstractAnnotatedAggregateRoot, and AbstractEventSourcedAggregateRoot are other implementation of AggregateRoot provided by Axon.
GenericJpaRepository
A type of Axon repository that uses JPA as it’s backing persisting technology. It has to be obviously configured with a JPA EntityManager.
EntityManagerProvider and SimpleEntityManagerProvider
EntityManagerProvider is an interface components that provides access to a an EntityManager. The SimpleEnttityManagerProvider is an implementation provided by the Axon Framework.
Summary
We now have a set up that allows us to send out commands, have the command handlers respond to the commands. Our command handler now uses Axon repository infrastructure to retrieve entities, change their state and persist it.
One important thing to note: This is not CQRS yet.
We have not hit the core of what CQRS is, which is the separation of the write/command infrastructure from the query/read infrastructure as we are still accessing the same physical store, we are still using the same model (the Account entity) for the view as we are using for the commands.
The next post will be Exploring CQRS with Axon Framework: Introducing Domain Events and Event Handling. and in it, we look at how to evolve our setup to be more in line with CQRS by introducing domain events and event handling.
1 comment:
I've found your article very useful. But I got one question. Should Aggregate really share his internal state like Debt in your example?
I thought that on command that was made whether Credit or Debit there should be emitted an event that the Account Debt has been changed. The proper EventListner gets that event and updates the QueryModel that provides the information of a current aggregate state?
Post a Comment