DDD - 如何强制执行不变量但特定于客户要求?

问题描述

我试图弄清楚如何使项目的少数消费者(商业客户)保持不变,他们对相同版本的聚合根有自己的要求。 让我们以客户为例,提出假设性问题以满足以下愚蠢的逻辑:

public class Customer 
{
  public Id { get; private set;}
  public string Name { get; private set;}

  public void SetName(string name){
     //client1 -> requires the name not to be null
     //client2 -> requires the name can start with "J"
     //client3 -> some other business logic
     this.Name = name;
  }
}

现在,我想到的是拥有这样的自定义验证/不变检查策略逻辑:

public void SetName(string name,INameCheckStrategy strategy){
    if(!strategy.IsSatisfiedBy(name)) throw new BusinessException("name does not meet the invariant check!");
     this.Name = name;
  }

哪里

public class Client1NameCheckStrategy : INameCheckStrategy {
   public bool IsSatisfiedBy(string name){
     return name != null;
   } 
}

任何想法如何处理这样的问题?

解决方法

您可以采用一种 DDDD(“动态域驱动设计”,用一句话来形容)方法,并将您域中的规则具体化为它们自己的聚合体,并将它们与客户相关联。如果您要对更改规则进行建模,这可能会使事情变得有趣(您如何处理使先前规则认为聚合有效的更改?)。

,

如果域模型实体在数据方面保持不变,并且数据对所有业务客户具有相同的含义,则使用类似策略模式 em>(如您所建议的)接缝非常合身。只需确保您不会让配置基础架构之类的任何内容泄漏到您的域模型中,并通过注入所需信息和客户端特定逻辑来严格控制。

如果您的聚合中有多个地方应用了此特定逻辑,您还可以考虑让存储库(或工厂)在从存储库集合中查询聚合时注入策略。

另一种选择是使用值对象的特定实现,它应该已经包含其数据的业务不变量。在您制作的示例中,可能有不同类型的 CustomerName 值对象(例如 CustomerNameClientX)。根据谁是当前客户,您可以确保创建了相应的客户名称值对象,并在创建过程中对其进行了验证并传递给了聚合。

,

我知道你问过 C# 甚至提到了 DDD,但如果我被要求真正帮助你,我会给你一个来自 JS 的例子。

function setName(name) {
    this.name = name;
}

没有类型检查,也不需要它们,因为在真正需要更多安全之前限制自己是一个坏习惯。

看,我只是使用了这个属性而没有检查它的类型,并且涵盖了我所有的业务案例:

function insertToMongoByName(db,collectionName,record) {
    console.log(`Saving record by name: ${record.name}`);
    record._id = record.name;        
    db.collection(collectionName).insertOne(record);
}

Mongodb 需要 _id 存在,所以我已经满足了这个不变量。

console.log 可以与任何东西一起工作,就像 JS 中的许多其他库一样(另请参阅我使用过的 insertOne 函数)。

这是名称的另一种用法:

_ = require("lodash");

function getEmailWithDisplayName(customer) {
    if(customer.fullEmail) {
        return customer.fullEmail;
    }

    if(!customer.email) {
        return undefined;
    }

    if(_.isString(customer.name)) {
        return `${customer.name} <${customer.email}>`;
    }
    else if(_.isObject(customer.name) && (customer.name.firstName || customer.name.lastName)) {
        const name = [customer.name.honorific,customer.name.firstName,customer.name.lastName]
            .filter(s => _.isString(s)).join(" ");
        return `${name} <${customer.email}>`;
    }

    return customer.email;
}

这次我不得不在 name 周围做一些类型检查,但它非常具体地针对我试图实现的目标。

在此之后,我对您的架构的问题是:您真的要修复您的 Customer 类以支持所有可能的未来需求吗?未来的所有用例都会乐于为检查提供“策略”,而不是按照他们的想法继续前进吗?

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...