Question

The commonly known advise in DDD is that an Aggregate Roots don't user a domain service. The domain service is to coordinate two Aggregate Roots to achieve a behavior.

It really surprised me when I saw this blog written by Rinat Abdullin with the title Building Blocks Of CQRS. Under the Domain Service section, you will read that a domain service is injected to an Aggregate Root.

Can an Aggregate Root accept a domain service?

Was it helpful?

Solution

Please, disregard that article. It was written a long time ago and is plain wrong. If implementing a module with AggregateRoot and DomainService patterns, I would recommend to have a higher logic (e.g. request handler) which is responsible for:

  1. Loading the aggregate
  2. Performing calculations with the help of domain services
  3. Mutating the aggregate state accordingly.

OTHER TIPS

In a way yes. If the AR really needs a service to do some of its job, then you can inject it as a method argument. If the AR needs a service for most of its behavior then probably it's modeled incorrectly.

I find the following explanation quite good. It's based on the book by Vaughn Vernon and 'injects' the domain service in the domain model through the method call that actually needs this service.

public class PurchaseOrder
{
    public string Id { get; private set; }
    public string VendorId { get; private set; }
    public string PONumber { get; private set; }
    public string Description { get; private set; }
    public decimal Total { get; private set; }
    public DateTime SubmissionDate { get; private set; }
    public ICollection<Invoice> Invoices { get; private set; }

    public decimal InvoiceTotal
    {
        get { return this.Invoices.Select(x => x.Amount).Sum(); }
    }

    public bool IsFullyInvoiced
    {
        get { return this.Total <= this.InvoiceTotal; }
    }

    bool ContainsInvoice(string vendorInvoiceNumber)
    {
        return this.Invoices.Any(x => x.VendorInvoiceNumber.Equals(
            vendorInvoiceNumber, StringComparison.OrdinalIgnoreCase));
    }

    public Invoice Invoice(IInvoiceNumberGenerator generator,
        string vendorInvoiceNumber, DateTime date, decimal amount)
    {
        // These guards maintain business integrity of the PO.
        if (this.IsFullyInvoiced)
            throw new Exception("The PO is fully invoiced.");
        if (ContainsInvoice(vendorInvoiceNumber))
            throw new Exception("Duplicate invoice!");

        var invoiceNumber = generator.GenerateInvoiceNumber(
            this.VendorId, vendorInvoiceNumber, date);

        var invoice = new Invoice(invoiceNumber, vendorInvoiceNumber, date, amount);
        this.Invoices.Add(invoice);
        DomainEvents.Raise(new PurchaseOrderInvoicedEvent(this.Id, invoice.InvoiceNumber));
        return invoice;
    }
}

public class PurchaseOrderService
{
    public PurchaseOrderService(IPurchaseOrderRepository repository,
        IInvoiceNumberGenerator invoiceNumberGenerator)
    {
        this.repository = repository;
        this.invoiceNumberGenerator = invoiceNumberGenerator;
    }

    readonly IPurchaseOrderRepository repository;
    readonly IInvoiceNumberGenerator invoiceNumberGenerator;

    public void Invoice(string purchaseOrderId,
        string vendorInvoiceNumber, DateTime date, decimal amount)
    {
        // Transaction management, along with committing the unit of work
        // can be moved to ambient infrastructure.
        using (var ts = new TransactionScope())
        {
            var purchaseOrder = this.repository.Get(purchaseOrderId);
            if (purchaseOrder == null)
                throw new Exception("PO not found!");
            purchaseOrder.Invoice(this.invoiceNumberGenerator,
                vendorInvoiceNumber, date, amount);
            this.repository.Commit();
            ts.Complete();
        }
    }
}

It's very hard to inject anything into domain objects, and doing so is quite technology specific. In java it requires compile time weaving of aspects into your domain classes. And although I could be mistaken on this, I think that most DDD leaders think that this is, generally, a bad idea. Both Evans and Vernon both actively discourage it, and I like to listen to them. For a full explanation, read Vernon.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top