在Java / spring中为多个用例验证同一bean的最佳方法是什么?

问题描述

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
        }
    } 
    

    n.b .:按照第三种方法(即使用验证)对用户进行注释 组


  • 最后一种方法

    使用功能编程类型:EitherRightLeftValueObject(我们可以从依赖项中获取其实现 之类的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);
       //...
    }
}