我正在设计库,它应该可以帮助我在未来项目中设计领域。我做了一些基本的价值对象,如电子邮件地址或电话号码。这些都很容易。当这样的值对象可能有不同的规则集时,它开始变得有问题,这些规则集说它是否有效。
让我们在这里以Password
为例,因为这是我写这个问题的实际原因。在以前的项目中,我在Password
解释器中验证了密码规则集。但它使这个实施项目变得具体,并且违反了 - 我认为 - 关注分离原则。
我正在考虑将战略帕滕作为这里的解决方案。但是以这种方式构建这样的 VO:new Password("123456", new AwfullyLoosePasswordValidationStrategy())
对我来说听起来很糟糕。我也在考虑 VO setValidationStrategy(PasswordValidationStrategy strategy)
中的静态设置器,但这仍然反对关注分离,对我来说听起来真的很奇怪。
所以响应的第一部分是 - 我认为 - 密码应该只做基本的健全性检查,就像isNullOrEmptyString
一样,仅此而已。但是我什么时候应该做适当的验证?在实体创建期间?在服务层的某个地方持久化之前?实体想法对我来说听起来还不错,但setPassword(Password password
怎么知道我想使用的策略?我尽量避免在我的项目中出现单例,几乎所有事情都是通过 DI 完成的。
tl;dr:我应该在哪里验证无法在其构造函数中验证的值对象?
我应该在哪里验证无法在其构造函数中验证的值对象?
我不相信你有这样的事情。 您永远不应该创建无效的值对象。 构造函数(如果您愿意,可以替换工厂或工厂方法)验证参数,如果它们可以接受,则创建一个格式良好、不可变的值对象,那么您就完成了。
我正在考虑将战略帕滕作为这里的解决方案。但是以这种方式构建这样的VO:新的密码("123456",新的AwfullyLoosePasswordValidationStrategy())对我来说听起来很糟糕。
我不知道策略模式在值对象中有意义的任何情况。 如果值类型的查询需要策略,则更有可能的是,您将策略作为查询的参数传递,而不是使其成为类型固有的。
也就是说,在我看来,你已经非常接近正确的想法;你只是倒退了。
PasswordValidator validator = new AwfullyLoosePasswordValidator();
Password password = validator.createPassword("123456");
也就是说,工厂根据您的策略验证输入,如果可以接受,则它将该输入传递给 Password 构造函数(它也执行自己的检查)。
或者,可以将密码策略作为实体创建的一部分实施,而不是密码创建的一部分
class EntityFactory {
private final PassswordValidator passwordValidator;
Entity create(Password password) {
passwordValidator.check(password);
return new Entity(password);
}
实体的想法对我来说听起来还不错,但是setPassword(密码密码)如何知道我想使用的策略?
现在这是一个非常重要的问题,你应该非常仔细地研究这个问题。
因为如果你仔细观察需求,你可能会发现 setPassword() 方法并不存在于明显的地方。
如果将密码建模为实体的属性,则实体还需要了解密码策略,以及如何知道? 更改密码策略是否需要更改每个实体?您可以有两个具有不同密码策略的实体等吗?
或者,系统中可能存在拥有密码策略的聚合。 在这种情况下,该聚合可能还负责所有密码(无论如何,所有密码都在该策略的范围内)。 也就是说,密码不是实体的属性,而是字典中的一个值,您可以在字典中使用 entityId 查找它。
用你无处不在的语言进行挖掘,并与你的领域专家一起审查需求是 ddd 的重点。 不要羞于确保您对自己的域有透彻的了解。
您可以使用构建器模式创建复杂的对象。
公共类密码{ 私人字符串密码;
private Password(String password){
}
public static class Builder{
private String password;
private Validation validation;
public Builder(){
}
public Builder setPassword(String password){
this.password = password;
}
public Builder setValidation(Validation validation){
this.validation = validation;
}
public Password build(){
if (null == validation){
return null;
}
if (validation.validate(password)){
return new Password(password);
}
else{
return null;
}
}
}
}
然后,您可以将其用作
Password password = new Password.Builder().setPassword("123456")
.setValidation( new AwfullyLoosePasswordValidationStrategy())
.build();
你的问题听起来不像是你想走DDD的方式,但你用DDD标签标记了你的问题 - 所以我假设你想要一个DDD答案:-)。
首先,不要将复杂的验证放在 VO 中。这将使它们难以使用。VO 本质上是一个简单的概念,所以尽量保持这种方式。特别是添加对服务的引用(如您建议的策略)通常是一个坏主意。
在哪里放置值对象验证
因此,您应该只验证最基本的内容,例如 VO 中的空检查。恕我直言,正则表达式检查已经太多了,但我敢肯定对此有不同的看法。
现在,VO 中没有验证逻辑,因此需要准备好查看无效的 VO。您的域模型应在 VO 进入域后立即对其进行验证,例如,在将 VO 作为参数的实体方法中。
使验证逻辑可重用
如果将 VO 验证逻辑放在实体方法中,则很快就会出现要验证来自另一个实体方法的相同 VO 的情况。处理此问题的最佳方法是规范模式。
此答案提供了模式的描述。规范的好处是它们是可重用和可组合的,例如,从一些较小的规范中制作更大的规格。此外,他们倾向于记录验证如何很好地适用于特定 VO,并且它们可以依赖于服务。
对于具有不同密码策略的特定情况,您可以只实现一堆不同的规范,每个策略一个规范。你可以组合更简单的来制作更复杂的。