Skip to content

Instantly share code, notes, and snippets.

@beyond-code-github
Last active August 29, 2015 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beyond-code-github/11209174 to your computer and use it in GitHub Desktop.
Save beyond-code-github/11209174 to your computer and use it in GitHub Desktop.

Domain Driven Dilemmas

I'm fairly new to domain driven design and I've been scratching my head for a while over this issue.

Here are my domain rules

  • A Record entity has a document of key value pairs
  • We can update the document for a record by providing the updated key value pairs
  • When we update a document we must calculate some values according to some parsed expressions.

This all needs to happens in-process within a single transaction.

Here are the options I've been reviewing:

Domain Events (A la Udi Dahan)

Here we use a static class with a 'Raise' method to raise events in-process to be executed synchronously. NB: Reference to complex object in event is ok as we remain in-process.

var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));

repository.Update(record);


void UpdateDocument(Document updates) 
{
    this.MergeUpdates(updates);
    Events.Raise(new DocumentUpdatedEvent(this));
}

Pros:

  • Logic is encapsulated within the Domain in the Entity.

Cons:

  • Domain now depends on infrastructure
  • May need to take extra steps to guarantee order of handlers of our event if there are more than one.
  • Not easy to see exactly what happens at a glance when updating a document
  • Domain Events is an overloaded term possible not well used
  • Changes to entity occur as side effects

Domain Commands (a variant on Udi Dahan's Domain Events)

Here we use a static class with an 'Issue' method that allows us to synchronously execute commands.

var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));

repository.Update(record);


void UpdateDocument(Document updates) 
{
    this.MergeUpdates(updates);
    DomainCommands.Issue(new UpdateExpressionsCommand(this));
}

Pros:

  • Logic is encapsulated withing the Domain in the Entity.
  • Order of execution is guaranteed
  • Easy to see what happens at a glance

Cons:

  • Domain depends on infrastructure
  • Changes to entity occur as side effects
  • Having commands in the domain is unfamiliar territory and questionable

Inject dependencies into Entity

Fairly straightforward... use a domain service and either pass dependencies via constructor or method signature

var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));

repository.Update(record);


void UpdateDocument(Document updates) 
{
    this.MergeUpdates(updates);
    this.MergeUpdates(this.expressionService.UpdateExpressions(this));
}

- or -

void UpdateDocument(Document updates, IExpressionService expressionService) 
{
    this.MergeUpdates(updates);
    this.MergeUpdates(expressionService.UpdateExpressions(this));
}

Pros:

  • No hidden side effects of call to expression service

Cons:

  • Entity now has to take a dependency.
  • Taking a dependency via method call mixes data and structure
  • Expression service will need itself to have a dependency on the parsing engine, so perhaps is not a true Domain Service in this sense

Defer to Command Handler

In this approach we let the command handler execute the methods in sequence:

var record = this.recordRepository.GetRecordById(message.Id);
record.UpdateDocument(Document.Parse(message.Json));

var app = this.appRepository.GetByIdentifier(message.AppIdentifier);
record.RecalculateExpressions(app.GetExpressions());

repository.Update(record);

Pros:

  • Easy to follow

Cons:

  • Need to call Recalculate in any command handler that updates a document.
  • Logic has leaked out of the domain
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment