具有不同状态的值对象的验证 - 密码大小写
Validation of Value Object with different stategies - password case
我正在设计库,它应该可以帮助我在未来的项目中设计域。我制作了一些基本的值对象,例如 EmailAddress 或 PhoneNumber。那些很容易。当这样的值对象可能有不同的规则集来说明它是否有效时,它开始给我带来问题。
让我们在这里使用 Password
作为示例,因为这是我写这个问题的实际原因。在以前的项目中,我在 Password
构造器中验证了密码规则集。但它使这个实施项目变得具体,并且违反了 - 我认为 - 关注分离原则。
我正在考虑将 Strategy Patten 作为这里的解决方案。但是以这种方式构建这样的 VO:new Password("123456", new AwfullyLoosePasswordValidationStrategy())
对我来说听起来很糟糕。我也在考虑 VO setValidationStrategy(PasswordValidationStrategy strategy)
中的 static setter,但这仍然违反关注点分离并且对我来说听起来很奇怪。
所以响应的第一部分是——我认为——密码应该只做基本的完整性检查,比如 isNullOrEmptyString
,仅此而已。但是我什么时候应该进行适当的验证?在实体创建期间?在服务层的某个地方坚持之前?实体的想法对我来说听起来还不错,但是 setPassword(Password password
) 如何知道我想使用的策略?我尽量避免在我的项目中使用单例,几乎所有的事情都是通过 DI 完成的。
tl;dr:我应该在哪里验证无法在其构造函数中验证的值对象?
您可以使用 Builder pattern 创建复杂的对象。
public class密码{
私人字符串密码;
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,并且他们可以依赖于服务。
对于具有不同密码策略的特定情况,您可以实施一系列不同的规范,每个策略一个规范。你还可以把简单的组合成更复杂的。
Where should I validate Value Object that cannot be validated in its constructor?
我不相信你有这样的东西。您永远不应该创建无效的值对象。构造函数(如果您愿意,可以替代工厂或工厂方法)验证参数,如果它们是可接受的,则创建一个格式正确、不可变的值对象,然后您就完成了。
I was thinking of Strategy Patten as solution here. But construction such VO in this way: new Password("123456", new AwfullyLoosePasswordValidationStrategy()) sounds terrible to me.
我不知道策略模式在值对象中有意义的任何情况。如果值类型的查询需要策略,您似乎更有可能将策略作为查询的参数传递,而不是将其作为类型固有的。
也就是说,在我看来,您已经非常接近正确的想法了;你只是倒退了
PasswordValidator validator = new AwfullyLoosePasswordValidator();
Password password = validator.createPassword("123456");
也就是说,工厂根据您的策略验证输入,如果可以接受,则将该输入传递给密码构造函数(它也执行自己的检查)。
或者,您可以将密码策略作为实体创建的一部分而不是密码创建的一部分来实施
class EntityFactory {
private final PassswordValidator passwordValidator;
Entity create(Password password) {
passwordValidator.check(password);
return new Entity(password);
}
Entity idea sounds not that bad for me, but how setPassword(Password password) would know about strategy I want to use?
现在那个是一个非常重要的问题,你应该非常仔细地研究这个问题。
因为如果你仔细看需求,你可能会发现 setPassword() 方法并不在明显的地方。
如果您将密码建模为实体的 属性,那么该实体还需要了解密码策略,它应该如何知道?更改密码策略是否需要更改每个实体?您可以拥有两个具有不同密码策略等的实体吗?
或者,您的系统中可能有一个集合拥有密码策略。在这种情况下,该集合也可能负责所有密码(无论如何都在该策略的范围内)。也就是说,密码不是实体的 属性,它实际上可能是字典中的一个值,您可以在字典中使用 entityId 来查找它。
ddd 的重点是使用您的通用语言进行挖掘并与您的领域专家一起审查需求。不要羞于确保您对自己的领域有透彻的了解。
我正在设计库,它应该可以帮助我在未来的项目中设计域。我制作了一些基本的值对象,例如 EmailAddress 或 PhoneNumber。那些很容易。当这样的值对象可能有不同的规则集来说明它是否有效时,它开始给我带来问题。
让我们在这里使用 Password
作为示例,因为这是我写这个问题的实际原因。在以前的项目中,我在 Password
构造器中验证了密码规则集。但它使这个实施项目变得具体,并且违反了 - 我认为 - 关注分离原则。
我正在考虑将 Strategy Patten 作为这里的解决方案。但是以这种方式构建这样的 VO:new Password("123456", new AwfullyLoosePasswordValidationStrategy())
对我来说听起来很糟糕。我也在考虑 VO setValidationStrategy(PasswordValidationStrategy strategy)
中的 static setter,但这仍然违反关注点分离并且对我来说听起来很奇怪。
所以响应的第一部分是——我认为——密码应该只做基本的完整性检查,比如 isNullOrEmptyString
,仅此而已。但是我什么时候应该进行适当的验证?在实体创建期间?在服务层的某个地方坚持之前?实体的想法对我来说听起来还不错,但是 setPassword(Password password
) 如何知道我想使用的策略?我尽量避免在我的项目中使用单例,几乎所有的事情都是通过 DI 完成的。
tl;dr:我应该在哪里验证无法在其构造函数中验证的值对象?
您可以使用 Builder pattern 创建复杂的对象。
public class密码{ 私人字符串密码;
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 的情况。 处理此问题的最佳方法是规范模式。
对于具有不同密码策略的特定情况,您可以实施一系列不同的规范,每个策略一个规范。你还可以把简单的组合成更复杂的。
Where should I validate Value Object that cannot be validated in its constructor?
我不相信你有这样的东西。您永远不应该创建无效的值对象。构造函数(如果您愿意,可以替代工厂或工厂方法)验证参数,如果它们是可接受的,则创建一个格式正确、不可变的值对象,然后您就完成了。
I was thinking of Strategy Patten as solution here. But construction such VO in this way: new Password("123456", new AwfullyLoosePasswordValidationStrategy()) sounds terrible to me.
我不知道策略模式在值对象中有意义的任何情况。如果值类型的查询需要策略,您似乎更有可能将策略作为查询的参数传递,而不是将其作为类型固有的。
也就是说,在我看来,您已经非常接近正确的想法了;你只是倒退了
PasswordValidator validator = new AwfullyLoosePasswordValidator();
Password password = validator.createPassword("123456");
也就是说,工厂根据您的策略验证输入,如果可以接受,则将该输入传递给密码构造函数(它也执行自己的检查)。
或者,您可以将密码策略作为实体创建的一部分而不是密码创建的一部分来实施
class EntityFactory {
private final PassswordValidator passwordValidator;
Entity create(Password password) {
passwordValidator.check(password);
return new Entity(password);
}
Entity idea sounds not that bad for me, but how setPassword(Password password) would know about strategy I want to use?
现在那个是一个非常重要的问题,你应该非常仔细地研究这个问题。
因为如果你仔细看需求,你可能会发现 setPassword() 方法并不在明显的地方。
如果您将密码建模为实体的 属性,那么该实体还需要了解密码策略,它应该如何知道?更改密码策略是否需要更改每个实体?您可以拥有两个具有不同密码策略等的实体吗?
或者,您的系统中可能有一个集合拥有密码策略。在这种情况下,该集合也可能负责所有密码(无论如何都在该策略的范围内)。也就是说,密码不是实体的 属性,它实际上可能是字典中的一个值,您可以在字典中使用 entityId 来查找它。
ddd 的重点是使用您的通用语言进行挖掘并与您的领域专家一起审查需求。不要羞于确保您对自己的领域有透彻的了解。