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必须为空,用户名必须为有效用户名(
2.更新用户(id必须是有效id,用户名必须是有效用户名(
第一种方法(NAIVE方法(:将每个用例的验证逻辑放入服务方法中。例如:
public void createUser(User user){ //validation logic here //other business logic }
第二种方法(使用注释(:使用hibernate的开箱即用注释(
@NotNull
、@Null
…(或hibernate自定义注释(例如,在这种情况下,我可以创建一个注释@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 ... }
这里的问题是,实体现在违反了单一责任原则,并且知道它将如何在不同的情况下使用案例
第四种方法(手动调用验证器(:
与第一种方法相同,但验证逻辑未投入使用方法,但是我从服务手动调用
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 } }
n.b.:用户被注释为第三种方法,即使用验证组
最后一种方法:
使用功能编程类型:
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... }
所以现在我们在
User
中有一个字段UsernameVO username
,而不是简单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);
//...
}
}