我正在尝试找出如何使项目的一些消费者(业务客户)保持不变,这些消费者对同一版本的聚合根有自己的要求。 让我们以客户为例,提出假设性问题来满足以下愚蠢的逻辑:
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(“动态域驱动设计”,创造一个短语)方法,将域中的规则具体化为它们自己的聚合,并将它们与客户相关联。 如果您要对更改规则进行建模,这可能会让事情变得有趣(您如何处理使聚合被先前规则视为有效的更改?)。
如果领域模型实体在数据方面保持相同,并且数据对于所有业务客户具有相同的含义,那么使用类似策略模式(如您所建议的)之类的东西似乎很合适。只需确保您不会让配置基础设施等任何内容泄漏到您的域模型中,并通过注入所需的信息和客户端特定逻辑来严格执行。
如果聚合中有多个位置应用此特定逻辑,您还可以考虑让存储库(或工厂)在从存储库集合查询聚合时注入策略。
另一种选择是使用值对象的特定实现,无论如何它应该已经包含其数据的业务不变量。在您编造的示例中,可能存在不同类型的 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
类以支持所有可能的未来需求吗?所有未来的用例都会乐意提供检查“策略”,而不是按照他们的想法前进吗?
我知道这是一个老问题,但在这里为其他可能过来的人留下答案:
这可能是规范模式的一个很好的例子,并存储“IsCustomerValid”规范的“客户端特定”配置。
类似:
var specification = _validationFactory.CustomerSpecification(clientId);
if(!specification.IsSafisfiedBy(customer))
throw CustomerDataInvalidException("...");
如果您想发挥创意,您可以对规范模式进行调味以返回结果类型而不仅仅是布尔值,这样您就可以返回一条有意义的错误消息,说明输入如何不适合特定的规范实例。