Hello, I am Misty.

In daily interface development, in order to ensure the stability and security of the interface, we generally need to deal with two kinds of verification in the interface logic:

  1. Parameter calibration
  2. Service rule verification

First, let’s look at parameter verification.

Parameter calibration

Parameter verification is easy to understand, for example, verify that the user name and password are empty when you log in to the system, and verify that the email and mobile phone numbers are correct when you create a user.

Validation of parameters is also very simple to implement. We just need to use Bean Validation framework, which provides Validation annotations to easily complete parameter Validation.

Common validation annotations are:

@Null,@NotNull,@AssertTrue,@AssertFalse,@Min,@Max,@DecimalMin,@DecimalMax,@Negative,@NegativeOrZero,@Positive,@PositiveOrZero,@Size,@Digits,@Past,@PastOrPresent,@Future,@FutureOrPresent,@Pattern,@NotEmpty,@NotBlank,@Email
Copy the code

Integrating parameter validation in SpringBoot I have written a special article for you to read. SpringBoot how to verify parameters, old birds are so play!

Next, let’s look at business rule validation.

Service rule verification

Service rule verification means that interfaces must meet certain service rules. For example, users in the service system must be unique. User attributes must not conflict with other users, and user names, mobile phone numbers, and email addresses of existing users must not be duplicated.

This requires that when creating a user, you need to verify whether the user name, mobile phone number, email is registered; You cannot modify information to the properties of an existing user when editing a user.

95% of programmers choose to write in service logic when faced with business rule validation. Common code logic is as follows:

public void create(User user) {
    Account account = accountDao.queryByUserNameOrPhoneOrEmail(user.getName(),user.getPhone(),user.getEmail());
    if(account ! =null) {
        throw new IllegalArgumentException("User already exists, please re-enter"); }}Copy the code

Although I showed in my last article that using Assert to optimize code makes it look cleaner, leaving the simple Validation to Bean Validation and the complex Validation to yourself is a programmer’s version of the short-handed and short-handed story.

The most elegant way to do this would be to reference Bean Validation in a standard way, using custom Validation annotations to validate business rules.

Next we use the user interface example mentioned above to validate business rules with custom annotations.

The code field

Requirements are easy to understand. When registering a new user, it should be restricted from duplicating key information of any existing user. When you modify your own information, it must be the same as your own information and cannot be changed to the information of an existing user.

These constraint rules not only serve these two methods, they may be used at other points in the user’s resources, or even in other layers of code, all of which can be covered by validation on the Bean.

Custom annotations

First we need to create two custom annotations for business rule validation:

  • UniqueUser: Indicates that a user is unique, including the user name, mobile number, and email address
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {

    String message(a) default"User names, mobile phone numbers and email addresses must not be duplicated with existing users."; Class<? >[] groups()default {};

    Class<? extends Payload>[] payload() default {};
}

Copy the code
  • NotConflictUser: indicates that the information of a user is non-conflicting. Non-conflicting means that the sensitive information of the user does not overlap with that of other users
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
    String message(a) default"User name, email address, mobile phone number duplicate with existing user"; Class<? >[] groups()default {};

    Class<? extends Payload>[] payload() default {};
}
Copy the code

Implement service verification rules

To enable custom validation annotations, implement the ConstraintValidator interface. The first parameter of the interface is the custom annotation type, and the second parameter is the class of the annotated field. Since we need to validate multiple parameters, we pass the user object directly. One thing to note is that the implementation class of the ConstraintValidator interface does not need to add @Component. It is already loaded into the container at startup.

@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T.User> {

    protected Predicate<User> predicate = c -> true;

    @Resource
    protected UserRepository userRepository;

    @Override
    public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
        return userRepository == null || predicate.test(user);
    }

    /** * Check whether the user is unique * that is, check whether the database has the information of the current new user, such as user name, mobile phone, email */
    public static class UniqueUserValidator extends UserValidation<UniqueUser>{
        @Override
        public void initialize(UniqueUser uniqueUser) {
            predicate = c -> !userRepository.existsByUserNameOrEmailOrTelphone(c.getUserName(),c.getEmail(),c.getTelphone());
        }
    }

    /** * Check whether there is any conflict with other users * change the user name, email, and phone number to a unique user name, or to a unique user name */
    public static class NotConflictUserValidator extends UserValidation<NotConflictUser>{
        @Override
        public void initialize(NotConflictUser notConflictUser) {
            predicate = c -> {
                log.info("user detail is {}",c);
                Collection<User> collection = userRepository.findByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
                // There is no conflict when you change the username, email, and phone number to one that is completely different from the existing one, or only the same as your own
                return collection.isEmpty() || (collection.size() == 1&& collection.iterator().next().getId().equals(c.getId())); }; }}}Copy the code

The Predicate functional interface is used here to determine business rules.

use

@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
    @Autowired
    private UserRepository userRepository;
    

    @PostMapping
    public User createUser(@UniqueUser @Valid User user){
        User savedUser = userRepository.save(user);
        log.info("save user id is {}",savedUser.getId());
        return savedUser;
    }

    @SneakyThrows
    @PutMapping
    public User updateUser(@NotConflictUser @Valid @RequestBody User user){
        User editUser = userRepository.save(user);
        log.info("update user is {}",editUser);
        returneditUser; }}Copy the code

It’s easy to use, just add custom annotations to the methods, and don’t need to add any business rule code to the business logic.

test

If the following error occurs after the interface is invoked, the service rule verification takes effect.

{
  "status": 400."message": "User names, mobile phone numbers and email addresses must not be duplicated with existing users."."data": null."timestamp": 1644309081037
}
Copy the code

summary

With the above steps, the business validation is completely separated from the business logic. When validation is required, it can be triggered automatically with the @validated annotation or manually triggered by the code. Depending on the requirements of your project, these annotations can be applied to the code at any level, such as the controller, service layer, persistence layer, etc.

This approach is more elegant than any business rule validation method and is recommended for use in your projects. At development time, you can place formatting validation annotations with no business meaning above the Bean’s class definition, and validation with business logic outside the Bean’s class definition. The difference is that annotations placed inside the class definition run automatically, while annotations placed outside the class need to be explicitly marked, as in the previous code.

Tips: Old bird series source code has been uploaded to GitHub, need to pay attention to this public account and reply keyword 0923 to obtain the source address.