1

假设我有一个实现以下业务规则/策略的域服务:

如果“家庭”类所有产品的总价超过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(请注意,在域服务中再次过滤产品(内存中)也无济于事,因为调用者可能只传递了所有产品的一个子集。
  • 应用层不知道哪些产品发生了变化,因此被迫全部保存,这可能是很多多余的工作。

问:有没有更好的策略来处理这种情况?

我想到了一些复杂的事情,例如调整基于持久性的存储库,使其在域服务中显示为基于集合的存储库,在内部跟踪由域服务加载的产品,以便在域服务时再次保存它们返回。

4

1 回答 1

2

首先,我认为为这种逻辑选择域服务——它不属于一个特定的聚合——是一个好主意。

而且我也同意你的观点,域服务不应该关心保存更改的聚合,将这样的东西保留在域服务之外还允许你关心管理事务 - 如果需要 - 应用程序。

我会对这个问题采取务实的态度,并对您的实现进行一些小改动以保持简单:

// 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()方法并保存所有更改的产品确实可以只驻留在应用层。

在考虑域模型纯度与域模型完整性方面(参见下面关于DDD 三难困境的讨论)这将使实现再次向纯度方向移动一点,但如果循环遍历产品并调用 ApplyFamilyDiscount() 就是它所做的一切(考虑到通过存储库获取相应产品已完成),也会使域服务有问题事先在应用层和产品列表已经传递给域服务)。同样,没有教条的方法,了解不同的选择及其权衡是相当重要的。例如,人们还可以考虑让产品始终通过在询问价格时应用所有适用的可能折扣来按需计算当前价格。但同样,这种解决方案是否可行取决于具体要求。

于 2021-04-26T05:32:14.797 回答