Внедрение доменного сервиса в AggregateRoots в DDD

Общепринятый совет в DDD состоит в том, что Aggregate Roots не используют доменную службу. Доменная служба должна координировать два Агрегированных Корня для достижения поведения.

Меня очень удивило, когда я увидел этот блог, написанный Ринатом Абдуллином с названием Building Blocks Of CQRS. В разделе "Служба домена" вы прочтете, что служба домена вводится в совокупный корень.

Может ли Aggregate Root принимать доменную службу?

4 ответа

Решение

Пожалуйста, не обращайте внимания на эту статью. Это было написано давным-давно и совершенно неправильно. При реализации модуля с шаблонами AggregateRoot и DomainService я бы рекомендовал иметь более высокую логику (например, обработчик запросов), которая отвечает за:

  1. Загрузка агрегата
  2. Выполнение расчетов с помощью доменных сервисов
  3. Мутируя агрегатное состояние соответственно.

В некотором смысле, да. Если AR действительно нужен сервис для выполнения какой-либо своей работы, вы можете добавить его в качестве аргумента метода. Если AR нуждается в сервисе для большей части своего поведения, то, вероятно, он неправильно смоделирован.

Я считаю следующее объяснение довольно хорошим. Он основан на книге Вона Вернона и "внедряет" доменную службу в модель домена посредством вызова метода, который действительно нуждается в этой услуге.

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();
        }
    }
}

Очень сложно внедрить что-либо в доменные объекты, и это зависит от конкретной технологии. В Java это требует компиляции аспектов времени в классы вашего домена. И хотя я могу ошибаться в этом, я думаю, что большинство лидеров DDD считают, что это, как правило, плохая идея. И Эванс, и Вернон оба активно препятствуют этому, и мне нравится их слушать. Для полного объяснения прочитайте Вернона.

Другие вопросы по тегам