0

I'm creating a domain service for each of my aggregate roots in order to facilitate some logic that won't be able to fit inside the entity themselves. I would rather not implement a factory and service for each aggregate root.

Also, I do not want to expose my repositories to application services directly. My repositories will have an exposed IQueryable. The services will hide this from the application layer by defining the query and returning paged results.

Do you see an anti-pattern with implementing a domain service as a factory?

public class AdministrationAccountService
{
    private readonly ISecurityUnitOfWork unitOfWork;
    private readonly IDomainEventDispatcher dispatcher;

    public AdministrationAccountService(
            ISecurityUnitOfWork unitOfWork, 
            IDomainEventDispatcher dispatcher
    ) {
        this.unitOfWork = unitOfWork;
        this.dispatcher = dispatcher;
    }

    public AdministrationAccount Lookup(string username)
    {
        var account = unitOfWork.AdministrationAccountRepository.GetByUsername(username);
        dispatcher.RaiseAsync(new AccountRecordOpenedEvent(account));
        return account;
    }

    public AdministrationAccount RegisterNewAccount(string username, string displayName)
    {
        var account = new AdministrationAccount(username, displayName);
        unitOfWork.AdministrationAccountRepository.Add(account);
        unitOfWork.Commit();
        return account;
    }
}
  • " I would rather not implement a factory and service for each aggregate." Why? Also, why do you need each for every entity? Only create one if entity needs it. – Euphoric Mar 27 '18 at 17:56
  • I don't need them for every entity, but I will need them for almost every aggregate root. I'll be making use of a lot of domain events even for look-ups. As far as not creating separate factory and service, It seems to me they use the same services. – Todd Skelton Mar 27 '18 at 18:04
  • 1
    Where does it say in DDD that every aggregate root requires a factory and a service? DDD is a design technique; the manner in which you implement that technique is entirely up to you. Naturally, some techniques may be a better fit to DDD principles than others, but I consider the direct mapping of DDD constructs to coding constructs an anti-pattern, since DDD isn't actually a coding technique. – Robert Harvey Mar 27 '18 at 19:43
  • There isn't anything in this example that I recognize as following the usual patterns of a domain service (as described by Evans), so it isn't clear which question you are asking. – VoiceOfUnreason Mar 30 '18 at 12:36
3

Let's start with a brief discussion of what a Domain Service is, and why you would want to use one. Because I would hesitate, upon an initial inspection, to call your example a Domain Service.

Domain Services are most often employed to solve two problems: When you want to extract some sort of calculation or other side-effect-free process into an object of it's own to be used/re-used by many pieces of your domain, or when there exists business logic about the behavior of some process that naturally spans multiple Entities. The former has many obvious manifestations, but the quintessential example of the latter is for the coordination of transactions that occur between Entities. That is, the execution of the business process requires the execution of commands to more than one Entity.

Say, you have an Atm and a CheckingAccount. Withdrawing money from the Atm requires knowledge that the CheckingAccount has sufficient funds to complete the transaction. Because Atm.WithdrawCurrency( decimal amount ) is completely separate from CheckingAccount.Debit( decimal amount ) but both need to be coordinated to complete the transaction, a Domain Service is an apt solution. In this case the AtmService will hold the knowledge regarding whether or not we will be dispensing money.

Aside from the above considerations, Domain Services will only serve to make your model more anemic. It can be incredibly tempting to button up all of the behavior of your system into discrete "services" that simply act on your model, but that is the antithesis of DDD and only hurts in the long run.

Looking at your example, does it contain business logic (we will get to that Event in moment)? Not really. Your methods seem to simply be coordinating between your Entities and the outside world. There aren't really any decisions being made. This reads more like an Application Service (retrieve piece of model -> execute command -> save).

As for that Event, I would refrain from raising Events from an Application Service. If, for some reason, you need there to be an Event recording when an AdministrationAccount is "opened", try to make it more explicit:

// AdministrationAccount
public void Open() 
{
    this.state = this.state.Open(this);
}

then in it's state:

// ClosedAdministractionAccountState
public IAdministrationAccountState Open( AdministrationAccount account )
{
    DomainEvents.RaiseAsync(new AccountRecordOpenedEvent(this));

    return new OpenedAdministrationAccountState(account);
}

The example above is using a FSM, but I could conceive of other options here. The important thing is that the logic and Event is squarely inside your domain where they belong. Not floating out in some Service method where they will inevitably be bypassed.

As for your other RegisterNewAccount method. Is this really the right place to create the account? When you talk about a new account being opened in the office, do you say "The AdministrationAccountService registered a new account with a username and displayName"? Or do you say, "An AdminUser registered a new account"? What I'm getting at here is that the method could easily be refactored to be part of what ever is holding the username and displayName before registration. E.g. AdminUser.Register(). Accounts (and their info) don't come out of nowhere. You can check out this link for a bit more: http://udidahan.com/2009/06/29/dont-create-aggregate-roots/

Refactoring towards the above helps purify and distill your application service.

As for whether or not a Domain Service can also be a factory? I'd say, generally speaking, no. Is it conceivable? Sure, but you will be better off refactoring your model towards deeper insight such that it isn't necessary. This kind of question is almost always a sign of a deficient model. Not some missing technical detail.

  • The eventing is pretty much the entire reason for having this service. Most events are contained in the AdministrationAccount but I need I way to track when the accounts are pulled (queried) by another user. It's hard to force an event on retrieval of an object without using the service that retrieves it. I see where you are going with the Open() method, but where would I call this, so it wouldn't be bypassed? – Todd Skelton Mar 30 '18 at 23:17
  • @ToddSkelton Define "queried". It seems unlikely to me that a user would even know what that means. What I am trying to do here is parse out if there exists a difference between physically "retrieving" an account from the disk, or going a step further and also "viewing" that account. Does this rule apply to you/other developers when you are testing (I'd assume at some point you will/have retrieved accounts from the disk)? Do you see what I am saying here? Normally, I would say that the retrieval of data is outside of your domain. It doesn't know or care about how it retrieved or persisted... – king-side-slide Mar 30 '18 at 23:33
  • @ToddSkelton ... If this rule doesn't truly apply to the low-level process of the actual retrieval, but rather, the higher-level process of users viewing information, you could make it impossible to view the data associated with an account unless it's in the "open" state. How is this event processed? What happens after dispatch? – king-side-slide Mar 30 '18 at 23:36
  • @ToddSkelton If you made a search-results page that listed several accounts, would you need to raise this event on every account returned? Or just the one the user decided to click on? Just spit-balling here... getting those DDD juices flowing – king-side-slide Mar 30 '18 at 23:41
  • The dispatcher will look for all event handlers and execute the handle method on each. Event handlers could really do anything with the event. Send an email notifying the account has been opened. Create an entry in an access log. Start a time tracking event then ends when the user leaves the record. Events will not be raised on search results, only when a record is clicked on. – Todd Skelton Mar 30 '18 at 23:45

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.