问题描述
TL; DR:从“简洁代码”的角度来看,针对不同用例对任何对象实施验证的最佳方法是什么?
要澄清一下,假设我有这个实体:
public class User {
private Integer id;
private String username;
//getters,setters ...
}
以及此服务类(也可以是接口):
public class UserService{
public void createuser(User user){
//...
}
public void updateUser(User user){
//...
}
}
和这两个用例:
1。。创建用户(id必须为null,用户名必须为有效的用户名)
2。 更新用户(ID必须为有效ID,用户名必须为有效用户名)
-
第一种方法(NAIVE方法):将每个用例的验证逻辑放在服务方法中。例如:
public void createuser(User user){ //validation logic here //other business logic }
-
第二种方法(使用注释): 使用休眠的现成注释(
@NotNull
,@Null
,...)或hibernate custom annotations(例如,在这种情况下,我可以创建注释@Username
根据我的应用需求验证用户名)。 在这种情况下,User
如下所示: 公共类用户{@Id private Integer id; @Username private String username; //getters,setters ... } //and the service methods will have `@Valid` before the parameters to be validated: public class UserService{ public void createuser(@Valid User user){ //... } public void updateUser(@Valid User user){ //... } }
这里的问题是,相同的验证逻辑适用于创建和更新用例,这不是我上面的要求所暗示的。
-
第三种方法(对每个用例使用带有验证组的注释):
public class User { @Id(OnUpdate.class) @Null(OnCreate.class) private Integer id; @Username //no need to specify groups bcz this field has same validation logic for all cases private String username; //getters,setters ... }
这里的问题是该实体现在violates the Single Responsibility Principle and knows how it will be used in different cases。
-
第四种方法(手动致电验证者):
与第一种方法相同,但验证逻辑未投入使用 方法,但是我从服务中手动调用
Validator
方法:public class UserService{ public void createuser(User user){ //call validator.validate(user) here //other business logic } public void updateUser(User user){ //call validator.validate(user) here //other business logic } }
-
最后一种方法:
使用功能编程类型:
Either
,Right
,Left
,ValueObject
(我们可以从依赖项中获取其实现 之类的vavr.io,因为它们尚不支持)abstract class ValueObject<L,R>{ Either<L,R> value; //L (left) for storing invalid value //R (right) for storing valid value } class UsernameVO extends ValueObject<ValueFailure<String>,String>{ private UsernameVO(Either<ValueFailure<String>,String> value){ this.value = value; } public static UsernameVO create(String name){ //factory method calls the private constructor //... } //other private methods for validation } class ValueFailure<T>{ String reason; T FailedValue; //getters,setters... }
所以现在我们在
UsernameVO username
中有一个字段User
,而不是 简单的String username
在这种情况下,服务如下:
public class UserService{ public void createuser(User user){ //use "fold" method on each Either type to validate it user .usernameVO. .value .fold(stringValueFailure -> { //throw error },validValue -> { //do nothing return null; }); } public void updateUser(User user){ //same as above } }
解决方法
对于所有的bean验证用例,没有最佳方法。您需要权衡利弊。违反您所举示例的单一责任原则不会引起太多问题。 但是,如果您的bean验证具有超过2/3的不同情况,并且实体更大,那么我建议采用以下方法。
public class User {
// Add hibernate annotations only for the common cases.
private Integer id;
@UserName
private String username;
//getters,setters ...
}
然后使用以下类进行更全面的验证。
public class UserValidator {
public static final int CREATE = 0;
public static final int UPDATE = 1;
// and so on ...
public static void validate(User user,int useCase) throws Exception{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (!violations.isEmpty())
throw new Exception("User object failed base validations");
switch (useCase){
case CREATE:
createCase(user);
break;
case UPDATE:
updateCase(user);
break;
}
}
public static void createCase(User user) throws Exception{
//additional validations for this use case
}
public static void updateCase(User user)throws Exception{
//additional validations for this use case
}
// add similar functions for different cases ...
}
服务类将类似于-
public class UserService{
public void createUser(User user){
UserValidator.validate(user,UserValidator.CREATE);
//...
}
public void updateUser(User user){
UserValidator.validate(user,UserValidator.UPDATE);
//...
}
}