In Vaughn Vernon's Implementing Domain-Driven Design
book, he described the use of factory method in an Aggregate Root. One example was that of a Forum
aggregate root which had startDiscussion
factory method which returned a Discussion
aggregate root.
public class Forum extends Entity {
...
public Discussion startDiscussion(
DiscussionId aDiscussionId, Author anAuthor, String aSubject) {
if (this.isClosed()) {
throw new IllegalStateException("Forum is closed.");
}
Discussion discussion = new Discussion(
this.tenant(), this.forumId(), aDiscussionId, anAuthor, aSubject);
DomainEventPublisher.instance().publish(new DiscussionStarted(...));
return discussion;
}
How would one implement this factory pattern in an event sourcing system, specifically in Axon?
I believe conventionally, it may be implemented in this way:
StartDiscussionCommand
-> DiscussionStartedEvent
-> CreateDiscussionCommand
-> DiscussionCreatedEvent
We fire a StartDiscussionCommand
to be handled by the Forum
, Forum
then publishes a DiscussionStartedEvent
. An external event handler would catch the DiscussionStartedEvent
, convert it, and fire a CreateDiscussionCommand
. Another handler would instantiate a Discussion
using the CreateDiscussionCommand
and Discussion
would fire a DiscussionCreatedEvent
.
Alternatively, can we instead have:
StartDiscussionCommand
-> CreateDiscussionCommand
-> DiscussionCreatedEvent
We fire StartDiscussionCommand
, which would trigger a command handler and invoke Forum
's startDiscussion() method that will return the CreateDiscussionCommand
. The handler will then dispatch this CreateDiscussionCommand
. Another handler receives the command and use this to instantiate Discussion
. Discussion
would then fire the DiscussionCreatedEvent
.
The first practice involves 4 DTOs, whilst the second one involves only 3 DTOs.
Any thoughts on which practice should be preferred? Or is there another way to do this?
The best approach to a problem like this, is to consider your aggregates (in fact, the entire system) as a black box first. Just look at the API.
Given a Forum (that is not closed),
When I send a StartedDiscussionCommand for that forum,
A new Discussion is started.
But also
Given a Forum that was closed
When I send a CreateDiscussionCommand for that forum,
An exception is raised
Note that the API you suggested is too technical. In 'real life', you don't create a discussion, you start one.
This means state of the forum is involved in the creation of the discussion. So ideally (when looking into the black box), such a scenario would be implemented in the Forum aggregate, and apply an event which represents the creation event for a Discussion aggregate. This is under the assumption that other factors require the Forum and Discussion to be two distinct aggregates.
So you don't really want the Command handler to return/send a command, you want that handler to make a decision on whether to create an aggregate or not.
Unfortunately, Axon doesn't support this feature, yet. At the moment, Axon cannot apply an event that belongs to another aggregate, through its regular APIs.
However, there is a way to get it done. In Axon 3, you don't have to apply
an Event, you can also publish one directly to the Event Bus (which in the case of Event Sourcing would be an Event Store implementation). So to implement this, you could directly publish a DomainEventMessage that contains a DiscussionCreatedEvent. The ID for the discussion can be any UUID and the sequence number of the event is 0, as it is the creation event of the discussion.