问题描述
如果“家庭”类别的所有产品总价超过100万,超过一年的家庭产品降价50%。
使用基于集合的存储库
我可以简单地创建一个域服务,使用规范模式加载“家庭”类别中的所有产品,然后检查条件,如果为真,则降低价格。由于产品由基于集合的存储库自动跟踪,因此域服务根本不需要发出任何显式基础设施调用 - 应该如此。
使用基于持久性的存储库
我运气不好。我可能会使用存储库和规范在我的域服务中加载产品(和以前一样),但最终,我需要发出不属于域层的 Save
调用。
我可以在应用层加载产品,然后将它们传递给领域服务,最后再将它们保存在应用层,就像这样:
// Somewhere in the application layer:
public void ApplyProductPriceReductionPolicy()
{
// make sure everything is in one transaction
using (var uow = this.unitOfWorkProvider.Provide())
{
// fetching
var spec = new FamilyProductsspecification();
var familyProducts = this.productRepository.findBySpecification(spec);
// business logic (domain service call)
this.familyPriceReductionPolicy.Apply(familyProducts);
// persisting
foreach (var familyProduct in familyProducts)
{
this.productRepository.Save(familyProduct);
}
uow.Complete();
}
}
但是,我发现此代码存在以下问题:
- 加载正确的产品现在是应用层的一部分,所以如果我需要在其他用例中再次应用相同的策略,我需要重复一遍。
- 规范 (
FamilyProductsspecification
) 和政策之间的内聚性丢失,本质上允许某人将错误的产品传递到域服务中。请注意,在域服务中再次过滤产品(内存中)也无济于事,因为调用者可能只传递了所有产品的一个子集。 - 应用层不知道哪些产品发生了变化,因此被迫保存所有这些产品,这可能是很多多余的工作。
问题:有没有更好的策略来处理这种情况?
我想到了一些复杂的事情,比如调整基于持久性的存储库,使其作为基于集合的存储库出现在域服务中,在内部跟踪域服务加载的产品,以便在出现时再次保存它们域服务返回。
解决方法
首先,我认为为这种逻辑选择域服务 - 它不属于一个特定的聚合 - 是一个好主意。
而且我也同意您的观点,域服务不应该关注保存更改的聚合,将此类内容保留在域服务之外还允许您关注管理事务 - 如果需要 - 应用程序。
对于这个问题,我会很务实,并对您的实现进行一些小的更改以使其简单:
// Somewhere in the application layer:
public void ApplyProductFamilyDiscount()
{
// make sure everything is in one transaction
using (var uow = this.unitOfWorkProvider.Provide())
{
var familyProducts = this.productService.ApplyFamilyDiscount();
// persisting
foreach (var familyProduct in familyProducts)
{
this.productRepository.Save(familyProduct);
}
uow.Complete();
}
}
在产品域服务中的实现:
// some method of the product domain service
public IEnumerable<Product> ApplyFamilyDiscount()
{
var spec = new FamilyProductsSpecification();
var familyProducts = this.productRepository.findBySpecification(spec);
this.familyPriceReductionPolicy.Apply(familyProducts);
return familyProducts;
}
因此,浏览所有超过一年的家庭产品然后应用当前折扣 (50%) 的整个业务逻辑都封装在域服务中。应用层再次只负责编排以正确的顺序调用正确的逻辑。您希望通过提供参数来使域服务方法的命名和通用程度当然需要调整,但如果无论如何只有一个特定的业务需求,我通常会尽量避免过于通用。因此,如果这是当前的家庭产品折扣,我就已经知道我需要在哪里更改实现 - 仅在域服务方法中。
老实说,如果应用程序方法没有变得更复杂并且您没有不同的分支(例如 if 条件),我通常会像您最初提出的那样开始,因为应用程序层方法也只是调用域具有相应参数的服务(在您的情况下是存储库)并且其中没有条件逻辑。如果它变得更复杂,我会将其重构为域服务方法,例如我提议的方式。
注意:由于我不知道 FamilyPriceRedcutionPolicy 的实现,我只能假设它会在产品聚合上调用相应的方法,让它们对价格应用折扣。例如。通过在 Product 聚合上使用诸如 ApplyFamilyDiscount() 之类的方法。考虑到这一点,考虑到遍历所有产品并调用折扣方法将只是聚合之外的逻辑,具有从存储库获取所有产品的步骤,调用 ApplyFamilyDiscount() 方法所有产品和保存所有更改的产品确实可以驻留在应用层。
在考虑域模型纯度与域模型完整性(见下面的讨论)方面,这将使实现再次朝着纯度方向移动一点,但如果循环,也会使域服务有问题通过产品并调用 ApplyFamilyDiscount() 就是它所做的一切(考虑到通过存储库获取相应产品是在应用程序层预先完成的,并且产品列表已经传递给域服务)。同样,没有教条式的方法,了解不同的选择及其权衡是相当重要的。例如,还可以考虑通过在询问价格时应用所有适用的可能折扣,让产品始终根据需求计算当前价格。但同样,这样的解决方案是否可行取决于具体要求。