Search code examples
design-patternsdomain-driven-designrepository-patternsoftware-designaggregateroot

DDD How to design services/aggregate roots involving related entities


I'm building an invitation system in which admins can only invite

  • users that are not already invited
  • users that are not already in the system (=already a member)

This flow involves two entities Invitation and User.

  • Deleting a user should not delete invitations he has sent
  • Updating the user's FirstName should not update all his invitations

This seems to indicate that Invitations and User should be added to two separated aggregates.

Then the only way to implement the logic described above is to use a domain service like IInvitationService. Am I right?

This interface could come with two methods:

public interface IInvitationService
{
    Task<Result> Create(Invitation invitation, CancellationToken token);
    Task<Result> Delete(Invitation invitation, CancellationToken token);
}

Finally, if I go for the service approach, I will have two possible "ways" to create invitations.

IInvitationRepository.Create() 
IInvitationService.Create()

Don't you think it is confusing?


Solution

  • Then the only way to implement the logic described above is to use a domain service like IInvitationService. Am I right?

    Something seems to be going very wrong here. In short, doesn't mean that we abandon the principles of object oriented design.

    Logic is normally implemented within the aggregate root that is changing. Part of the point of introducing the aggregate pattern was to make it easier to find the code responsible for making changes -- rather than having that same code scattered all over your model, you can just think about where the change of state is happening and know where to look for the code.

    The aggregate root itself knows only its own state - any other information needed to perform a change is passed to it as an argument.

    So I wouldn't normally expect that you need a "domain service" for the parts of the model you have described so far.

    Where a domain service is likely to come into play is the case where a change to one aggregate depends on the recent state of a different aggregate. For example, if a particular change to "invitation" depends on the current state of "user", then you might pass to the invitation method responsible for computing the change a domain service that gives access to a cached copy of the user's state.

    As for create... creation patterns are weird. Udi Dahan's post is a good starting point.