【发布时间】:2013-06-04 10:40:17
【问题描述】:
DDD 中众所周知的建议是聚合根不使用域服务。域服务是协调两个聚合根来实现一个行为。
当我看到 Rinat Abdullin 写的标题为 Building Blocks Of CQRS 的博客时,我真的很惊讶。在域服务部分下,您将看到域服务被注入到聚合根。
聚合根可以接受域服务吗?
【问题讨论】:
标签: domain-driven-design domainservices
DDD 中众所周知的建议是聚合根不使用域服务。域服务是协调两个聚合根来实现一个行为。
当我看到 Rinat Abdullin 写的标题为 Building Blocks Of CQRS 的博客时,我真的很惊讶。在域服务部分下,您将看到域服务被注入到聚合根。
聚合根可以接受域服务吗?
【问题讨论】:
标签: domain-driven-design domainservices
【讨论】:
在某种程度上是的。如果 AR 确实需要一个服务来完成它的一些工作,那么您可以将它作为方法参数注入。如果 AR 需要为其大部分行为提供服务,那么它的建模可能不正确。
【讨论】:
我觉得following explanation 非常好。它基于 Vaughn Vernon 的书,并通过实际需要该服务的方法调用将域服务“注入”到域模型中。
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();
}
}
}
【讨论】:
IInvoiceNumberGenerator 是域服务,PurchaseOrderService 是应用程序服务。您永远不应该将存储库注入到您的域服务中,因为它们是编排用例而不是业务逻辑所需要的。
请忽略那篇文章。这是很久以前写的,完全是错误的。如果使用 AggregateRoot 和 DomainService 模式实现模块,我建议使用更高的逻辑(例如请求处理程序)来负责:
【讨论】: