SpringBoot builds the back-end interface gracefully

Posted by convinceme on Fri, 17 Dec 2021 08:03:36 +0100

SpringBoot builds the back-end interface gracefully

directory structure
1. Validator parameter validation
2. Global exception handling
3. Unified data response

1. Preface:

A complete back-end interface is roughly divided into four parts: interface mapping address, request mode, request data and response data. How to build these four components, each company has different requirements. There is no "best" standard. But in general, it needs to be standardized.

1. Parameter verification

Generally, an interface pair needs to perform a qualitative parameter verification on the request parameters. The importance of parameter verification is needless to say, but how to verify parameters gracefully is more important.

1.1. Business layer parameter verification

1.1. 1. Traditional formal parameter verification

Let's first look at the following example of traditional interface request parameter verification

publi String addUser(User user){
    if(user == null || user.getId() == null || user.getName() == null || ...){

           return "User or field cannot be empty";
      }
      ......
}

There must be nothing wrong with using traditional parameter verification. However, when there are many object attributes in the request parameters, this writing will be very cumbersome. It's OK to use one interface, but how should we deal with it when multiple interfaces are used?

Therefore, we must talk about the transformation and upgrading of business layer parameter verification.

The Validator and Hibernate parameter verification tools in Spring are recommended here. You only need to import the required dependencies to use.

1.1. 2. Validator and BindingResult combined parameter verification

Define a User object entity class

@Data

public class User{

    @NotNull(message = "user id Cannot be empty")
    private Long id;

   @NutBlank(message = "User name cannot be empty")
   privae String userName;

   @NutBlank
   @Size(min = 6, max = 11, message = "Password length must be 6~11 position")
   private String password;

   @NutBlank(message = "User mailbox cannot be empty")
   @Email(message = "The mailbox format is incorrect")
   private Stirng email;
}

Various parameter verification forms are used in the above entity classes, @ NotBlank can not only verify non empty types, but also prevent passing a non empty string. Using this parameter verification, we do not need to carry out those cumbersome parameter verification in the Service implementation process of each interface.

Create call interface

@RestController
@RequestMapping("/user")
public class Usercontroller{

    @Autowired
    private UserService userService;
    
   @PostMapping("/addUser")
   public String saveUser(@RequestBody @Valid User user, BingindResult bResult){

      // If the parameter verification fails, the error information will be encapsulated as an object and assembled in BindingResult
      for(ObjectError error : bindingResult.getAllErrors()){
         return error.getDefaultMessage();
      }
      return userService.addUser(user);
   }

}

In the above interface, when the request parameters added with parameter verification come to the interface, the Validator will automatically complete the verification and mark it as passed with @ Valid. After that, we only need to return some error information to execute other business codes. It eliminates a large section of cumbersome parameter verification logic.

In fact, we don't need to use BindingResult to encapsulate the error information, and we can also use custom exceptions to throw the error information we need.

1.1. 3. Advantages and disadvantages of Validator and BindingResult joint parameter verification

    1,Simplify the code and improve the readability of the code.

    2,Many parameter verification rules are provided, which is convenient and fast to use.

    3,Reduce coupling and use Validator It can make the business layer focus on business logic and separate from the basic parameter verification logic.

    4,Disadvantages: each interface needs to pass more than one BindingResult Parameters are still not concise and efficient enough.

2. Global exception handling

Global Exception handling: when we request the interface, the parameter verification will throw a MethodArgumentException parameter verification Exception, and we may throw custom RunntimeException, system Exception and other Exception information in the business layer. If we can expect a unified Exception handling tool to help us handle all kinds of Exception information, That is, global Exception handling.

@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user){
    return userService.addUser(user);
}

In the above logic, we do not use the parameter verification result error encapsulation class. A MethodArgumentNotValidException exception (request parameter verification exception) will be thrown here, and we did not catch the exception information.

So, how should we handle this exception?

Next, we can use the global exception handling in springBoot for exception handling.

@ControllerAdvice
@Slf4j
public class WikiExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(Exception e){
        log.error("System service exception:{}", e.getMessage());
        e.printStackTrace();
        return Result.exception().message("Execute global exception");
    }

    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public Result error(ArithmeticException e){
        e.printStackTrace();
        return Result.exception().message("implement ArithmeticException abnormal");
    }

    @ExceptionHandler(WikiException.class)
    @ResponseBody
    public Result error(WikiException e){
        log.error("Business processing exception:{}", e.getMsg());
        e.printStackTrace();
        return Result.exception().code(e.getCode()).message(e.getMsg());
    }

    /**
    * The parameter verification processing is consistent with the information in the entity. Here, the parameter exception response 1001 is unified
    * @param e
    * @return
    */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result validExceptionHandler(BindException e){
        String msg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        log.warn("Parameter verification exception:{}", msg);
        return Result.exception().code(1001).message(msg);
    }
}

In the above class, we identify this class as a global Exception handling class by adding the "@ RestControllerAdvice" annotation, which includes Exception handling of Exception system service, ArithmeticException arithmetic Exception handling, WikiException (custom thrown Exception: inherited from RunntimeException) business Exception handling and BindException parameter verification Exception handling.

By handling exceptions globally, we can handle all exceptions uniformly, and then return the error message prompt to the front end. However, we only returned the prompt content of an error message, and did not return some unified response message bodies to the front end.

This requires the following unified response format.

3. Unified data response

The unified data response body is a response body that contains some specific formats, such as the boolean ID of whether the operation is successful, the returned status code, the returned message prompt message, and the contained data.

package com.song.wiki.utils;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
* ClassName: Return results: unified encapsulation class
* Author: Pull the wind
* Date: 2020
* Copyright: 2020 by Bluff version 1.0
* Description:
**/

@Data
@AllArgsConstructor
public class Result {

    /** Is it successful*/
    private Boolean success;

    /** Return status code*/
    private Integer code;

    /** Return content*/
    private String message;

    /** Return Map data set*/
    private Map<String, Object> data = new HashMap<String, Object>();


    /**
    * Empty parameter structure
    */
    private Result(){}

    /**
    * Successful operation method
    * @return Encapsulated Result, you can customize and modify the data
    */
    public static Result ok(){
        Result r = new Result();
        r.setSuccess(ResultCode.OK_SUCCESS.success());
        r.setCode(ResultCode.OK_SUCCESS.code());
        r.setMessage(ResultCode.OK_SUCCESS.message());
        return r;
    }

    /**
    * Operation failed method
    * @return Encapsulated Result, you can customize and modify the data
    */
    public static Result error(){
        Result r = new Result();
        r.setSuccess(ResultCode.OPERATION_FAIL.success());
        r.setCode(ResultCode.OPERATION_FAIL.code());
        r.setMessage(ResultCode.OPERATION_FAIL.message());
        return r;
    }

    /**
    * Exception handling method
    * @return Encapsulated Result, you can customize and modify the data
    */
    public static Result exception(){
        Result r = new Result();
        r.setSuccess(false);
        r.setCode(ResultCode.OPERATION_FAIL.code());
        r.setMessage("System exception, please contact the administrator");
        return r;
    }
    
    public Result success(Boolean success){
        this.setSuccess(success);
        return this;
    }
    
    public Result message(String message){
        this.setMessage(message);
        return this;
    }

    public Result code(Integer code){
        this.setCode(code);
        return this;
    }

    public Result data(String key, Object value){
        this.data.put(key,value);
        return this;
    }

    public Result data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

By establishing the above unified data response format, we can transfer the same data response format in the process of data interaction at the front and rear ends.

In this way, the front end and the back end can reach an agreement on the data response format, which not only simplifies the workload of our back end, but also reduces the fast processing and pre rendering of data by its front-end workers;

At the same time, it also improves the overall development efficiency of the project.

epilogue

1. The parameters are uniformly verified through Validator, and the back-end interface is annotated with @ Valid annotation;
2. The user-defined exception is used to throw an exception, and the global exception handling tool handles the exception information uniformly;
3. Unify the data response format and improve the overall development efficiency of the project;

Multi dimensional front and rear end development specification, elegant creation of back-end interface, standardization of front and rear end data response, and improvement of overall development efficiency of the project.
Part of the content comes from other articles and is compiled with project examples.

Topics: Java Spring RESTful