0

Recently began implementing domain events in my domain layer which really allows me to decouple the application from the domain. Eventually, I would like to get a full-scale CQRS w/Event Sourcing infrastructure.

The question I have is that I have an Account class and one of the use cases is to be able to rename it. However, there is a domain constraint that says "no two accounts can have the same name". Based on my quick design I am assuming that this should be implemented with a domain service that checks if the account name is already taken, and if not then rename the account. I thought maybe I could get away from a domain service by using domain events, but all my domain event handlers reside in the application layer (not the domain layer). I obviously don't want to have any business logic in the application layer.

Am I understanding the difference between domain services and domain events correctly? It seems to me (based on what I've read) is that domain events allow you to decouple the application layer from the domain layer, but when it comes to ensuring consistency between multiple aggregates, I need to use domain services. Or is this possible to do it with domain events and keep everything isolated to the domain layer? My Account class looks like:

public class Account
{
    public string Name { get; private set; }

    public Account(string name) { // blah // }

    public void AddSubAccount(Account subAccount)
    {
        // do work
    }

    public void RemoveSubAccount(Account subAccount)
    {
        // do work
    }

    public void Rename(string newName)
    {
        Name = newName;
        // How to ensure domain model invariants remain true?
        // No two accounts can have same name
        // Domain Event example:
        // DomainEvents.Publish(new AccountRenamedEvent(oldName, newName));
    }
}
  • Down-voter care to comment? – keelerjr12 Oct 6 '18 at 12:59
3

no two accounts can have the same name

So this is an expression of a constraint known as Set Validation. The short version is that you have two choices: you accept the fact that the constraint doesn't always hold, and organize your processes to detect and compensate for conflicts; you lock the entire set when making a modification that is subject to the constraint.

For example, traditionally we might have modeled this as an Account entity, and used a transactional RDBMS with a unique constraint on the table. The database serializes each update made to it, and so the constraint can be checked against the entire set of account entities. The transaction mechanic acts as an effective lock of the entire set of entries.

Think about what happens if you now try to distribute those Accounts across two databases, while maintaining the constraint.

Greg Young really nailed it when he introduced this question into the mix

What is the business impact of having a failure?

There is no magic; only trade offs.

It seems to me (based on what I've read) is that domain events allow you to decouple the application layer from the domain layer, but when it comes to ensuring consistency between multiple aggregates, I need to use domain services. Or is this possible to do it with domain events and keep everything isolated to the domain layer?

Domain service is the right idea, but it isn't enough on its own.

That is to say, for your use case, the rename account code won't have a local copy of all of the restricted names, so passing a domain service with the capability to perform the uniqueness check is a clean design.

But the domain service is working with old information; it can't answer the question "is this new name unique", but only a weaker version "does this name collide with any that were in use 10 nanoseconds ago", or whatever. If the underlying data is subject to change between when you ask the question and when you use the answer, then you have a race condition, and the possibility of error.

To eliminate the race condition, you need to have some flavor of lock that protects you against concurrent change.

For example, the LMAX team looked at their architecture, and realized that having a single writer would solve their problems. Eliminating concurrent writes is equivalent to locking all of the data, and the system can thereby know that the answer hasn't changed while the question was in flight.

Today, you see similar choices being made with consensus systems - a number of distributed components elect a leader, and only that leader is allowed to propose changes to the data stream, and the leader ensures it only proposes changes consistent with prior history.

Domain Events should really only be used for what then?

Fowler: Captures the memory of something interesting which affects the domain

Eric Evans on Domain Events.

As a pattern, they are messages that describe a change, thereby allowing you to decouple the computation of a change from its consequences.

Among other consequences, you can re-read the domain event without repeating the change; which can be very useful in a system distributed across an unreliable network.

  • excellent answer, as always. So Domain Events should really only be used for what then? Sending emails? Updating ReadModels? Logging? – keelerjr12 Oct 6 '18 at 13:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.