2013-06-08 2 views
5

Общеизвестным советом в DDD является то, что агрегированные корни не используют службу домена. Служба домена должна координировать два сводных корня для достижения поведения.Внедрение службы домена в AggregateRoots в DDD

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

Может ли сводный корень принять услугу домена?

ответ

4

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

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

Ринат, разве вы не хотите размещать баннер на этих старых своих статьях, что повторит то, что вы написали в ответ? Люди часто ссылаются на ваш блог за идеями и передовыми методами, но вы, конечно же, знаете, что некоторые из описанных практик на самом деле не лучшие ... Не обижайтесь, просто предложение. –

3

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

+0

Я согласен с тем, что Эванс и Вернон не согласны с идеей Рината. В этом примере он использует услугу ценообразования, чтобы найти порог в методе LockCustomerForAccountOverdraft. Это оценка бизнес-правил, которую можно выполнить, отправив команду. С подходами Эвана и Версона должна существовать служба доменов более высокого уровня, называемая LockCustomer, где может быть выполнена координация PricingService и CustomerAggregate. Я думаю, что Эванс и Вернон применяют SRP, поэтому есть больше компонентов. Rinat использует простой подход, поэтому он может смириться с нарушением открытого/закрытого принципа в этом случае для Клиента. –

7

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

+0

Если обе службы живут в одном и том же ограниченном контексте, совокупные корни могут разговаривать друг с другом через сообщения, так зачем нужно вводить услугу? –

+1

Требуется ТОЛЬКО, если AR нуждается в этом, чтобы выполнить какое-либо поведение. Я не понимаю, как здесь поможет обмен сообщениями. – MikeSW

+0

Возможно, я использовал здесь неправильный термин. Я имел в виду события домена. –

2

Я нахожу following explanation неплохое. Он основан на книге Вона Вернона и «внедряет» службу домена в модели домена посредством вызова метода, который действительно нуждается в этой службе.

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

PurchaseOrderService - это служба приложений, а не служба домена, как я ее понимаю. –

+2

@wonderfulworld: IInvoiceNumberGenerator - это служба домена – mynkow