DDD shortly
Generally DDD is about domain model that, when expressed in Ubiquitous Language, can be shared by developers and domain experts allowing them to communicate effectively. From technical point of view DDD is about modeling your core domain using aggregate roots (AR), entities, value objects and some other artifacts like domain services and repositories. What is important, when applying DDD to medium or large domains (generally complex domains are good candidates for DDD), you should not be expecting one model to arise representing all areas covered by the system. Each area will likely be modeled separately (inside its own Bounded Context). This aspect is very important, especially if your application starts to grow covering more and more business activities. If you stick with one model aka EDM (Enterprise Domain Model) (that typically reassembles model of relations inside your sql database), you will end up with monolithic system not capable of adjusting to business requirements.Therefore building application DDD-way should be seen as completely different approach comparing to standard approach (one model to rule them all approach). You will need additional means to express communication between your different models (Bounded Contexts).
Thats where EDA - event based communication comes in.
Let's see how to apply DDD and EDA to standard ORM-based application with use of Axon.
Axon introduction
Axon framework provides building blocks for CQRS applications. Some blocks, i.e. (event sourcing, asynchronous processing of commands (no response from Command Handler)) are optional and can be omitted. If you are not ready for (or just don't need) full CQRS, you can still benefit from other goodies such as synchronous command handling, events, sagas and others. Additionally Axon integrates deeply with Spring making configuration a trivial task (just use built in namespaces (xml part) and annotations). Unique feature of Axon is that it allows to integrate existing JPA-based application. To make your entities become Aggregate Roots extend AbstractAggregateRoot class, to load your ARs from database use GenericJpaRepository (or even better HybridJpaRepository if you want to use EventStore). For detailed instructions see User's Guide. Lets start working with code.Get rid of Transaction Script
When applying DDD, we tend to build rich entities that encapsulate behavior.In contrast to standard approach with anemic domain model and procedural code inside Application Services (see: Transaction Script), most of business logic should be handled inside Aggregate Roots. When a command comes in, it is dispatched to Command Handler whose only job should be to get AR from Repository and invoke single method on it.
Lets imagine a system that manages user accounts (Account AR). One of Account's properties is state. To activate an account, AccountActivateCommand must be sent by the client. In order to handle this command, we must register and implement specialized Command Handler:
That's it. Nothing special. Clean code so far.
Implementing business logic - uniformed approach
First, we need to extend our model in order to make it more interesting :) Our system must support handling user's payments related to Payment Period. The following requirements are added:1) when Account is activated, Payment Period must be created that will be used to keep track of payments related to the Account
2) after Payment Period expires, subsequent Payment Period must be created, but expired Payment Period should still be accessible
We could model our Account like this:
In this model, Account is responsible for managing Payment Periods. It sounds good, we can implement account activation inside Account class:
Please notice three blocks of code in this method and theirs order. This separation is dictated by CQRS-based design. You should have all these blocks in every method that is invoked by Command Handler. The blocks are:
1) validation - checking if operation is allowed and will not break consistency of the Aggregate
2) raising event - creating a Domain Event containing information about Aggregate's change
3) handling event - updating state of the Aggregate (no exceptions, no logic here!)
Every change of Aggregate's state must be signaled with an Event (Domain Event). All Domain Events will be stored in Event Store (if configured) (just serialized as Blobs or Clobs to single table).
If our Aggregate Root is event-sourced (is reconstructed from Event Store instead or being created by EntityManager), we should move event handling code to separate method:
We will not discuss Event Sourcing in this article, as we don't want to get rid of our powerful ORM, or do we? (actually in CQRS world ORM is
persona non grata - you were warned ;)
Keep your ARs lously coupled
Going back to our model, after thinking a little bit longer, we realize that it doesn't fit well our needs... One of the requirements is to process commands related directly to Payment Periods. These commands should be dispatched directly to Payment Period entity rather than go through Account AR. Payment Period should be an AR on its own. When we think more about this (and talk with our Domain Expert (if we have one;)), the separation of Account and Payment Period will become even more obvious. We could easily imagine two services Account Service (responsible for accounts management) and Payment Service (responsible for payment registration) working independently.So we can simplify Account AR and upgrade Payment Period to AR:
Now account activation is implemented partially (Payment Period is not being created). We could implement Application Service that would first call Account#activate and than create new Payment Period, but implementing business logic within Application Service layer leads to Transaction Script that we are trying to avoid (we want to avoid both ARs (Account and Payment Period) forcibly be invoked in the same transaction - it hurts system scalability).
Lets think of our ARs on higher level. They belong to different contexts/services (virtual Account Service and Payment Service). The way to communicate between different contexts (services) is to use Domain Events!
Events to the rescue
We already have implemented raising of the AccountActivatedEvent in activate() method of Account AR. Now we must create Event Listener that will listen for this event and will send PaymentPeriodCreate command.With Axon it is as simple as creating new class:
Of course, we need to implement a command handler that will handle PaymentPeriodCreateCommand by creating Payment Period AR and adding it to Repository.
Thats all. Now we have independent ARs that communicate with events.
This design leads us in direction of autonomous components (services) communicating asynchronously in publish-subscribe model (aka: push integration model), possibly via some kind of EventBus or Broker (see: Avoid a Failed SOA: Business & Autonomous Components to the Rescue by Udi Dahan)
But we will not go so far.
There are many other benefits from applying EDA. One of them is ability to keep detailed history of Events:
History of Events (Audit)
By storing Events in database (Event Store) we keep log of changes. We can easily create additional table containing following data:- aggregate root class
- aggregate root id
- event class
- user
The table like this can serve basic reporting purposes. If we want to create more sophisticated reports in the future, we can reply events stored in Event Store and populate any report table we need. All history of changes is kept in Event Store.
Now lets see how we can model long running process with SAGA! In case you forgot the requirements, short reminder: new Payment Period must be created after the current one expires.
Enter SAGA
Saga is a stateful component (its state is persisted across invocations) that is capable of receiving events (including timeout events) (similar to Event Listener). Saga represents business process instance, in other words business process associated with particular AR(s).First method will be invoked on PaymentPeriodCreatedEvent and will result in creation of new Saga associated with Payment Period being created and related Account. Inside method body we schedule the PaymentPeriodExpiredEvent that will be triggered when validity interval of Payment Period ends (Axon provides Quartz-based implementation of Event Scheduler) .
The second method is called when payment expiration happens (when PaymentPeriodExpiredEvent is triggered by the Event Scheduler).
The only thing this method does is sending a command that will be processed by our Account Service (currentPaymentPeriod must be increased) and eventually by Payment Service (new Payment Period will be created).
Lets see how easy we can test our Saga with use of Test Fixture provided by Axon:
Finally, I want to discuss one more topic related to DDD.
Don't pollute your core domain model
What is common mistake DDD beginners make is that they try to apply DDD totally (put all application logic into ARs boundaries). Let's take an example and add new requirement to our application:
All entities (including Account) are separated by Sales Areas. Any operation on Account (creation, activation, etc.) can be performed only if the owning Sales Area is in status ACTIVE.
First, we will modify our model by adding the following JPA mapping to the Account AR:
Now lets think of the requirement. Where should we put the checking if the Sales Area is active? The activate() method of Account AR seems to be the perfect place. But if we think more, we realize that Sales Area does not belong to our core domain! Checking status of the Sales Area inside Account AR will pollute the code (checking must be done before any modification of Account's state). So our new model is broken! There should be no Account -> Sales Area mapping. But we can not remove it, because we reuse the same model for serving queries (we don't follow CQRS in this aspect) and we need to be able to filter Accounts by Sales Area easily.
Ok, so the better place to put the checking would be a Command Handler (AccountCommandHandler). But it may be necessary to reuse this logic across different commands. What we need is some kind of interceptor that will prevent particular commands (account related or other) reaching Command Handlers. Not surprisingly Axon provides CommandHandlerInterceptor interface that allows for customized command handler invocation chains. No example this time, as it is quite easy to imagine:)
If the requirement is that whenever Account is activated corresponding PaymentPeriod MUST be created, than why we allow to run both operations in separate transactions? From my understanding the business requirement is that both operations are atomic, so if PaymentPeriod creation fails the Account activation should fail otherwise we can have activated Account without PaymentPeriod, which from business point of view seems to be invalid.
ReplyDeleteAlso I do not understand why running both operations in, so called, Transaction Script hurts the scalability? There are thousands of applications written this way, and they scale fine. Could you explain why we hurt system scalability in this example?
You are right, that for typical (not collaborative) domains there is no need to create transaction boundaries between Aggregates. And Axon doesn't force you to do this (executing several commands in one transaction is still possible).
ReplyDeleteSystem as the whole should be eventually consistent. This is how business processes execute (not in single ACID transaction) (if you transfer money to a different bank is it handled in one transaction? ;-)
Scalability is affected when system can not be partitioned. If Aggregates are handled in separate transactions it is quite easy to put them on different partitions (no need for distributed transactions (2PC)).
Please also notice that increasing number of Aggregate Roots handled in one transaction increases probability of transaction lock (optimistic or pessimistic).
Please refer to BASE: An Acid Alternative for explanation of what BASE (basically available, soft state, eventually consistent) is and how does it affect scalability.
Some other aspects of transaction boundaries explained here:
http://www.udidahan.com/2011/03/05/entities-transactions-and-broken-boundaries/
Hi Michal,
ReplyDeleteyour question mostly relates to the CAP theorem. In a command handling component, you generally require full consistency and availability. That means you cannot guarantee partition tolerance.
The only safe way to get around this is to make sure all data lives on the same machine. If two entities are used inside a single transaction, it requires them to live on the same instance. That means all entities of a single SalesArea would need to be available (and exclusively used) on 1 machine.
There are thousands of applications written this way, and they scale fine.
I've been involved in the development and maintenance of lots of these applications. First of all, their scalability is severely limited. By adding caching all over the place these limits are stretched, making these systems overly complex to maintain. Secondly, probably more systems are set up using CQRS principles that you'd guess. Although often not called that way, almost all banks and trading companies use a CQRS based architecture.
Cheers,
Allard Buijze
Axon Framework Project Lead
Hi Pawel,
ReplyDeletenice post. Just a dumb question:
In PaymentPeriod class there is a reference to the Account aggregate root.
How did you manage to get a reference to the Account instance in EventHandler of the PaymentPeriodCreated event if it carry just the AccountId AggregateIdentifier property?
Maybe PaymentPeriod needs an instance of AccountRepository?
Thank you in advance
Regards
Domenico Giffone
I don't understand your question as there is no code in the examples where hander of PaymentPeriodCreated event gets reference to the Account AR. But I suppose you are missing this peace of explanation: constructor of PaymentPeriod is invoked by CommandHandler responsible for handling PaymentPeriodCreateCommand. The handler retrieves Account from AccoutRepository.
ReplyDeletePlease not that PaymentPeriod holds reference to Account only for querying purposes (ORM must know about Account - PaymentPeriod relationship). In true CQRS one AR can't hold reference to another AR.