Examples

To illustrate the use of the framework, this page shows the flow of one command through the application.

1. A user clicks "Change name" (of the questionnaire). The system asks what should be the new name and calls a method on the controller

2. The controller issues a command "RenameQuestionnaire", which is a domain command:

public ActionResult RenameQuestionnaire(Guid id, int version, string name)
{
    CommandIssuer.IssueCommand(new RenameQuestionnaire
    {
        AffectedAggregateRootId = id,
        AffectedVersion = version,
        Name = name
    });
    return null;
}

The Guid uniquely identifies the AggregateRoot. The version is needed to be able to resolve concurrency issues later. The Name property is the actual content of the command.

3. The CommandIssuer Publishes this to a ServiceBus

4. There is a CommandRouter instance which listens to the commands. It finds the CommandHandler and let it handle the command:

var handler = factory.CreateCommandHandlerFor(cmd);
handler.ExecuteCommand(cmd);

5. In this case the handler is a RenameQuestionnaireHandler object. It finds the questionnaire by it's Id and Version, and calls "UseName" on the object.

internal class RenameQuestionnaireHandler : CommandHandler<RenameQuestionnaire>
{
    protected override void ExecuteInternal(RenameQuestionnaire command)
    {
        var q = GetById<Questionnaire>(command.AffectedAggregateRootId, command.AffectedVersion);
        q.UseName(command.Name);
    }
}

6. The UseName method only checks if the name is correct (= not empty) and if it is, it will tell it's Aggregate to handle a new QuestionnaireNamedEvent.

public void UseName(string name)
{
    if (string.IsNullOrEmpty(name))
        throw new ValidationException("Name can't be empty.");

    HandleNewEvent(new QuestionnaireNamedEvent(name));
}
// ... in the base class:
protected void HandleNewEvent(DomainEvent ev)
{
    ev.AggregateRootId = Aggregate.AggregateRoot.Id;
    ev.Version = Aggregate.AggregateRoot.Version + 1; //the version after applying this event
    ev.Entitynumber = EntityNumber;

    Aggregate.Handle(ev);
}

As you can see, the Entities are quite aware of their Aggregate. This is required to trace the DomainEvents back to each Entity - since Entities can only be accessed via the AggregateRoot.

7. The QuestionnaireNamedEvent is handled by the Questionnaire Entity itself:

private void handleQuestionnaireNamedEvent(QuestionnaireNamedEvent ev)
{
    Name = ev.Name;
}

During this route that the Command and DomainEvent have taken, there have been objects eavesdropping on what happened:
  • The CommandHandler.GetById<T> method has recorded the affected AggregateRoot objects
  • The Aggregate.Handle method has recorded the events that happened in the Aggregate during the command

8. After the command finishes, the CommandHandler calls DomainRepository.SaveChanges for each affected AggregateRoot object.

9. The DomainRepository simply hands the recorded DomainEvents over to a DomainEventStorage

public void SaveChanges(AggregateRoot ar)
{
    domainEventStorage.Store(ar.Aggregate.UnCommittedEvents);
}

just for fun, now it is possible to recreate the object in this way:
private AggregateRoot recreate(Guid id, int version, Type aggregateRootType)
{
    var ar = GetById(id, 0, aggregateRootType); //version 0 means a new object will be created

    var events = domainEventStorage.GetEventsToRebuildAggregateRootUpToVersion(id, version);

    foreach (var ev in events)
    {
        ev.Replaying = true;
        ar.Aggregate.Handle(ev);
    }

    return ar;
}

10. In the meantime there is QuestionnaireDetailUpdater object which happens to listen (via databasepolling) to the QuestionnaireNamedEvent. When it notices the event, it will update it's naive, simple, ad hoc datamodel:

private void handle(QuestionnaireNamedEvent ev)
{
    CurrentRecord.Name = ev.Name;
}

The simplicity of the last code snippet is because a lot of the plumbing is done by the base class ViewModelUpdater in combination with NHibernate. It knows by a generic parameter which ViewModel is being updated and loads the correct record from the database by its AggregateRootId. After the DomainEvent is handled, it will tell NHibernate to commit changes to the database.

11. In the meantime the webpage is fanatically refreshing each 4 seconds to see if there are any changes in the ViewModel. The refresh issues a query and returns it as JSON. (ViewModel term is overloaded here, please don't be confused).

private ViewModel getViewModel(Guid id)
{
    var model = new ViewModel();
    model.Qnnaire =
        QueryFactory.CreateQuery<QueryQuestionnaireDetail>()
            .WhereAggregateRootIs(id)
            .Result();
    return model;
}

public ActionResult UpdatedViewModel(Guid id)
{
    return Json(getViewModel(id), JsonRequestBehavior.AllowGet);
}

12. JQuery merges any changes to the page.

Last edited Mar 5, 2010 at 9:39 PM by jwboer, version 4

Comments

No comments yet.