Search code examples
domain-driven-designevent-sourcingaggregaterootaxon

Can an Aggregate Root factory method return a command instead of publishing an event?


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?


Solution

  • 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.