preface

On a dark and windy night (please pretend not to see the time), my leader suddenly assigned me a requirement, as shown below

First of all, [Financial Services] is the function menu of one of our background management systems, and [Points Mall] is another system.

Then look at this requirement: it was obvious to maintain a [bank branch] function in our system, and then provide some interface for [points Mall] to use, so I designed the following two tables

Then I wrote down such an interface to connect with colleagues in integrals Mall

/ * *

* Points mall bank branch information Controller

 *

 * @author hcq

 * @date2020/5/9 roar,

* /


@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;



    / * *

* Get the list of bank branches through the bank branch business scope keyword

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public List<BankService> getAllBankService(String scope){

        QueryWrapper<BankService> wrapper=new QueryWrapper<>();

        wrapper.lambda().like(BankService::getScope,scope);

        return bankServiceMapper.selectList(wrapper);

    }



}

Copy the code

Unified Response object

After a while, my colleague suddenly came to me and said, “Is there something wrong with your interface? Why can’t I get the data?”

I told him: “You don’t talk nonsense, such a simple interface, how can there be a problem? Are you asking in the wrong way?”

And then he threw me a screenshot…

I saw what was going on, so I said to him, “Isn’t that all right? It’s normal logic to return an empty set with no data, and you even suspect there’s something wrong with my interface.

The colleague one face angrily excuse way: “you return this to me, HOW can I know is what meaning, I still thought request failed!”

After a heated discussion (I was whipped), I finally gave in and decided to send him a request status code so he could use it to determine whether his request was successful. So the interface looks like this

@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;



    / * *

* Get the list of bank branches through the bank branch business scope keyword

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public Map<String,Object> getAllBankService(String scope){

        Map<String,Object> result=new HashMap<>(2);

        QueryWrapper<BankService> wrapper=new QueryWrapper<>();

        wrapper.lambda().like(BankService::getScope,scope);

        result.put("resultCode"."00");

        result.put("data",bankServiceMapper.selectList(wrapper));

        return result;

    }



}

Copy the code

The returned data also looks like this

After a while, my colleague found me and told me that I still needed an interface to query the information of salesmen, so I developed another interface

@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;

    private final BankServiceSaleMapper bankServiceSaleMapper;





    / * *

* Get the list of bank branches through the bank branch business scope keyword

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public Map<String,Object> getAllBankService(String scope){

        Map<String,Object> result=new HashMap<>(2);

        QueryWrapper<BankService> wrapper=new QueryWrapper<>();

        wrapper.lambda().like(BankService::getScope,scope);

        result.put("resultCode"."00");

        result.put("data",bankServiceMapper.selectList(wrapper));

        return result;

    }



    / * *

* Obtain outlet information and salesman information through outlet ID

     * @paramBankId Id of the service node

* /


    @PostMapping("/getBankServiceDetail")

    public Map<String,Object> getDetail(String bankId){

        // Query network information

        BankServiceVo bankServiceVo=new BankServiceVo();

        BankService bankService = bankServiceMapper.selectById(bankId);

        BeanUtils.copyProperties(bankService,bankServiceVo);

        // Query the salesman information

        QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

        wrapper.lambda().eq(BankServiceSale::getBankServiceId,bankId);

        bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

        // Return data

        Map<String,Object> result=new HashMap<>(2);

        result.put("code"."00");

        result.put("data",bankServiceVo);

        return result;

    }

}

Copy the code

I learned smart this time, and had already returned the status code before he asked me, but I thought the previous status code called resultCode was not as simple as the code directly, so I changed the status code of the interface into the code field

“The status code fields of your two interfaces are different,” he said. “Do I have to make a separate judgment for each request??” .

So that’s the reason. So I went to my leader and explained the problem. The leader says, “To avoid a similar situation in the future, all developers must return the same set of status codes when writing interfaces. Small he, this matter since you put forward, that by you to be responsible for it.”

After serious consideration, since I want to restrict field names, I can not continue to use Map, not to mention the problem of poor readability, even the Map is not binding enough, I still have the risk of hand tremors, how can I strict others. After much consideration I have encapsulated a unified response object as follows

/ * *

* Interface unified response object

* /


@Data

public class ResultVo<T{



    / * *

* status code

* /


    private String code;



    / * *

* Response data

* /


    private T data;



    private ResultVo(String code, T data) {

        super(a);

        this.code = code;

        this.data = data;

    }



    public static <T>  ResultVo<T> buildSuccess(T data){

         return new  ResultVo<>("00",  data);

    }



}

Copy the code

Then modify the original two interfaces as follows

@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;

    private final BankServiceSaleMapper bankServiceSaleMapper;





    / * *

* Obtain branch information by branch business scope

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public ResultVo<List<BankService>> getAllBankService(String scope){

        QueryWrapper<BankService> wrapper=new QueryWrapper<>();

        wrapper.lambda().like(BankService::getScope,scope);

        return ResultVo.buildSuccess(bankServiceMapper.selectList(wrapper));

    }



    / * *

* Obtain outlet information and salesman information through outlet ID

     * @paramBankId Id of the service node

* /


    @PostMapping("/getBankServiceDetail")

    public ResultVo<BankServiceVo> getDetail(String bankId){

        // Query network information

        BankServiceVo bankServiceVo=new BankServiceVo();

        BankService bankService = bankServiceMapper.selectById(bankId);

        BeanUtils.copyProperties(bankService,bankServiceVo);

        // Query the salesman information

        QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

        wrapper.lambda().eq(BankServiceSale::getBankServiceId,bankId);

        bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

        return ResultVo.buildSuccess(bankServiceVo);

    }

}

Copy the code

At this point, the matter is closed.

Unified Exception Handling

Two days later, the same colleague came back to me and said, “Your interface still has a problem. No status code was returned when the interface reported an error, so it was impossible to determine whether the request was successful.”

I took a look at the screenshot and then looked at the code and knew I had written the BUG.

So we went back to the discussion (this time with a good apology and no flings) and added two interface rules

  • The interface should respond to the status code whether it succeeds or not
  • When an interface throws an exception, it should be explicitly prompted

Based on these two points, I added a Message field to the interface Unified Response Object class for exception messages.

/ * *

* Interface unified response object

* /


@Data

public class ResultVo<T{



    / * *

* status code

* /


    private String code;



    / * *

* Prompt message

* /


    private String message;



    / * *

* Response data

* /


    private T data;



    private ResultVo(String code, String message,T data) {

        super(a);

        this.code = code;

        this.message=message;

        this.data = data;

    }



    public static <T>  ResultVo<T> buildSuccess(T data){

        return new  ResultVo<>("00"."SUCCESS",  data);

    }



    public static <T>  ResultVo<T> buildException(String code,String message){

        return new  ResultVo<>(code, message,  null);

    }

}

Copy the code

Then the original two interfaces will look like this

/ * *

* Points mall network information

 *

 * @author hcq

 * @date2020/5/6 roar,

* /


@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;

    private final BankServiceSaleMapper bankServiceSaleMapper;





    / * *

* Obtain branch information by branch business scope

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public ResultVo<List<BankService>> getAllBankService(String scope){

        ResultVo resultVo;

        try {

            QueryWrapper<BankService> wrapper=new QueryWrapper<>();

            wrapper.lambda().like(BankService::getScope,scope);

            return ResultVo.buildSuccess(bankServiceMapper.selectList(wrapper));

        }catch (Exception e){

            e.printStackTrace();

            resultVo=resultVo.buildException("99"."System exception");

        }

        return resultVo;

    }



    @PostMapping("/getBankServiceDetail")

    public ResultVo<BankServiceVo> getDetail(String bankId){

        ResultVo resultVo;

        try {

            // Query network information

            BankServiceVo bankServiceVo=new BankServiceVo();

            BankService bankService = bankServiceMapper.selectById(bankId);

            if(bankService==null) {

                return resultVo.buildException("10000",String.format("Dot %s does not exist",bankId));

            }

            BeanUtils.copyProperties(bankService,bankServiceVo);

            // Query the salesman information

            QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

            wrapper.lambda().eq(BankServiceSale::getBankServiceId,bankId);

            bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

            return ResultVo.buildSuccess(bankServiceVo);

        }catch (Exception e){

            e.printStackTrace();

            resultVo=resultVo.buildException("99"."System exception");

        }

        return resultVo;

   }



}

Copy the code

Seeing this pile of code, I can’t help but think… Why does a simple business logic now seem so complicated that it is a little difficult to read the code written by yourself?

At this time, I understood: in the actual development process, there are often more abnormal business logic scenarios than normal business logic scenarios, and the complexity of business is generally reflected in the processing of abnormal business situations. If the business logic of the normal and abnormal business logic are mixed together, so developers will do when writing code, read part of scattered energy to handle exceptions logic, so focus on normal business logic processing will be dispersed, thus increasing the difficulty of coding, coding the difficulty of raised, so that it will greatly increase the risk of bugs. Is it possible to distinguish normal business logic from abnormal business logic?

Global exception handler

Spring helps us solve this problem: Spring added two annotations in version 3.2, @controlleradvice @exceptionhandler. With these annotations, we can implement a global ExceptionHandler that catches and handles exceptions when an interface throws them.

First, you define a handler for the global Exception, where @ExceptionHandler(exception.class) specifies the type of Exception to handle

/ * *

* Global exception handler

 * @author hcq

 * @date2020/5/9 11:35 a.m.

* /


@ControllerAdvice

@Slf4j

public class GlobalExceptionHandler {





    / * *

* Exception unified processing of exceptions

* /


    @ExceptionHandler(Exception.class)

    @ResponseBody

    public ResultVo handlerException(Exception e) {

        log.error("System exception",e);

        return ResultVo.buildException("99"."System exception");

    }





}

Copy the code

This simplifies our interface to something like this

/ * *

* Points mall network information

 *

 * @author hcq

 * @date2020/5/6 roar,

* /


@RestController

@RequestMapping("/bank/service")

@AllArgsConstructor

public class BankServiceController {



    private final BankServiceMapper bankServiceMapper;

    private final BankServiceSaleMapper bankServiceSaleMapper;





    / * *

* Obtain branch information by branch business scope

     *

     * @paramScope Indicates the scope keyword

* /


    @PostMapping("/getAllBankService")

    public ResultVo<List<BankService>> getAllBankService(String scope) {

        QueryWrapper<BankService> wrapper = new QueryWrapper<>();

        wrapper.lambda().like(BankService::getScope, scope);

        return ResultVo.buildSuccess(bankServiceMapper.selectList(wrapper));

    }



    / * *

* Obtain outlet information and salesman information through outlet ID

     * @paramBankId Id of the service node

* /


    @PostMapping("/getBankServiceDetail")

    public ResultVo<BankServiceVo> getDetail(String bankId) {

            // Query network information

            BankServiceVo bankServiceVo = new BankServiceVo();

            BankService bankService = bankServiceMapper.selectById(bankId);

            if (bankService == null) {

                return ResultVo.buildException("10000",String.format("Dot %s does not exist", bankId));

            }

            BeanUtils.copyProperties(bankService, bankServiceVo);

            // Query the salesman information

            QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

            wrapper.lambda().eq(BankServiceSale::getBankServiceId, bankId);

            bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

            return ResultVo.buildSuccess(bankServiceVo);

    }



}

Copy the code

Then try again to initiate a GET request

Can see the bank/service/getAllBankService this interface only supports a post request, so the server internal throws HttpRequestMethodNotSupportedException anomaly, and then you can see, This exception has been caught by the global exception handler and returns our unified response object

Custom exception

Here’s another problem: since the global Exception handler handles exceptions, this interface will return 99, system error for any exceptions that occur within the system. And Spring has solved all try-catches for us, but have you noticed that even without a try-catch, an interface can still contain exception logic?

  if (bankService == null) {

       return ResultVo.buildException("10000",String.format("Dot %s does not exist", bankId));

  }

Copy the code

That counts as…? Of course it counts… This type of exception is different in that it does not require try-catch handling and can return an explicit prompt. But at the end of the day, it’s also logic for handling exceptions

In this case, I can subdivide the exceptions so that each exception corresponds to a business scenario of the exception, and the corresponding status code and prompt information are maintained inside the exception. When this exception is thrown, the global exception handler returns the corresponding status code and prompt message as a unified response object

Top-level service exception

/ * *

* Service exception

 * @author hcq

 * @date 2020/5/8 12:02

* /


@Getter

@Setter

public class BusinessException extends RuntimeException{



    / * *

* Specific exception code

* /


    protected String code;



    / * *

* Exception information

* /


    protected String message;



    BusinessException(String code, String message){

        super(message);

        this.code=code;

        this.message=message;

    }



}

Copy the code

Custom service exceptions

/ * *

* Service exception: the node %s does not exist

 * @author hcq

 * @date2020/5/10"

* /


public class NotFindBankException extends BusinessException {



    public NotFindBankException(String bankId){

        super(10000,String.format("Dot %s does not exist",bankId));

    }

}

Copy the code

Service exception handler

@ControllerAdvice

@Slf4j

public class GlobalExceptionHandler {



   / * *

* Business exception handler

     * @paramE Service Exception

     * @returnUnified Response object

* /


    @ExceptionHandler(BusinessException.class)

    @ResponseBody

    public ResultVo handlerException(BusinessException e) {

        log.error("Abnormal Service",e);

        return  ResultVo.buildException(e.getCode(),e.getMessage())

    }



    / * *

* Exception unified processing of exceptions

* /


    @ExceptionHandler(Exception.class)

    @ResponseBody

    public ResultVo handlerException(Exception e) {

        log.error("System exception",e);

        return ResultVo.buildException("99"."System exception");

    }



}

Copy the code

interface

    @PostMapping("/getBankServiceDetail")

    public ResultVo<BankServiceVo> getDetail(String bankId) {

        // Query network information

        BankServiceVo bankServiceVo = new BankServiceVo();

        BankService bankService = bankServiceMapper.selectById(bankId);

        if (bankService == null) {

           throw new NotFindBankException(bankId);

        }

        BeanUtils.copyProperties(bankService, bankServiceVo);

        // Query the salesman information

        QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

        wrapper.lambda().eq(BankServiceSale::getBankServiceId, bankId);

        bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

        return ResultVo.buildSuccess(bankServiceVo);

    }

Copy the code

Postman initiates the request

At this point, we can use Spring’s global exception handler to completely strip exception logic away, and the developer only needs to focus on the normal business logic, which only needs to throw exceptions

Abnormal enumeration

Spring global exception Handler + custom exception, although very powerful, but its disadvantages are clear: with the continuous increase of business, custom exception classes will become very numerous. In addition, it can be found that these custom exception classes are usually only different from the status code and the message, so you can use an enumeration class to maintain all the exception status code and message, and then encapsulate these status codes and message as a service exception.

Business exceptions

/ * *

* Service exception

 * @author hcq

 * @date 2020/5/8 12:02

* /


@Getter

@Setter

public class BusinessException extends RuntimeException{



    / * *

* Specific exception code

* /


    protected String code;



    / * *

* Exception information

* /


    protected String message;



    BusinessException(String code, String message){

        super(message);

        this.code=code;

        this.message=message;

    }



}

Copy the code

Global exception handler

@ControllerAdvice

@Slf4j

public class GlobalExceptionHandler {



   / * *

* Business exception handler

     * @paramE Service Exception

     * @returnUnified Response object

* /


    @ExceptionHandler(BusinessException.class)

    @ResponseBody

    public ResultVo handlerException(BusinessException e) {

        log.error("Abnormal Service",e);

        return  ResultVo.buildException(e.getCode(),e.getMessage())

    }



    / * *

* Exception unified processing of exceptions

* /


    @ExceptionHandler(Exception.class)

    @ResponseBody

    public ResultVo handlerException(Exception e) {

        log.error("System exception",e);

        return ResultVo.buildException("99"."System exception");

    }



}

Copy the code

Abnormal enumeration

/ * *

* Exception enumeration

 * @author hcq

 * @date 2020/5/8 12:03

* /


@AllArgsConstructor

@Getter

public enum ExceptionAssertEnum implements ExceptionAssert {



    /*---------------- The payment system is abnormal ----------------*/

    NOT_FIND_BANK_EXCEPTION(10000."Dot %s does not exist");



    private final Integer code;

    private final String message;





}

Copy the code

Here you must be wondering why the enumeration class is named this way and implements an interface.

Here is actually comes from I accidentally saw an article: https://www.jianshu.com/p/3f3d9e8d1efa

I was amazed to see that although this article is through the exception enumeration + custom exception + global exception handler to handle exception logic, but the way the thrown exception is very different with me, I found that I before each throw exceptions there will always be an if judgement, and this article is similar to Assert thrown exception way makes me very shocked, So I took a leaf out of the book and abstracted enumerated objects into two interfaces, one for controlling access to properties of enumerated objects and one for controlling the behavior of enumerated objects.

Enumerate object behavior control

/ * *

* Exception behavior + exception attributes

 * @author hcq

 * @date 2020/5/8 12:00

* /


public interface ExceptionAssert extends ExceptionStatus {



    / * *

* Create exception

     * @paramArgs Prompt information

     * @return Exception

* /


    default BusinessException newException(Object... args){

        String message=String.format(getMessage(),args);

        return new BusinessException(getCode(),message);

    }



    / * *

* Non-null assertion

     * @paramObj assertion object

     * @paramArgs Prompt information

     * @throws BusinessException obj == null

* /


    default void notNull(Object obj,Object... args) {

        if (obj == null) {

            throw newException(args);

        }

    }



    / * *

* Assert a Boolean value

     * @paramObj assertion object

     * @paramArgs Prompt information

     * @throwsBusinessException obj is false

* /


    default void isTrue(boolean obj,Object... args) {

        if(! obj) {

            throw newException(args);

        }

    }



}

Copy the code

Enumerate object property control

/ * *

* Exception attributes

 * @author hcq

 * @date 2020/5/8 11:50

* /


public interface ExceptionStatus {



    / * *

* Get the exception status code

     * @return code

* /


    Integer getCode(a);



    / * *

* Obtain exception information

     * @return message

* /


    String getMessage(a);

}

Copy the code

interface

    / * *

* Obtain outlet information and salesman information through outlet ID

     * @paramBankId Id of the service node

* /


    @PostMapping("/getBankServiceDetail")

    public ResultVo<BankServiceVo> getDetail(String bankId) {

        // Query network information

        BankServiceVo bankServiceVo = new BankServiceVo();

        BankService bankService = bankServiceMapper.selectById(bankId);



        // Exception handling

        ExceptionAssertEnum.NOT_FIND_BANK_EXCEPTION.notNull(bankService,bankId);



        // Query the salesman information

        BeanUtils.copyProperties(bankService, bankServiceVo);

        QueryWrapper<BankServiceSale> wrapper = new QueryWrapper<>();

        wrapper.lambda().eq(BankServiceSale::getBankServiceId, bankId);

        bankServiceVo.setBankServiceSaleList(bankServiceSaleMapper.selectList(wrapper));

        return ResultVo.buildSuccess(bankServiceVo);

    }



}

Copy the code

You can see that the method of throwing exceptions has changed to a more concise one:

ExceptionAssertEnum.NOT_FIND_BANK_EXCEPTION.notNull(bankService,bankId)

Finally, he couldn’t stop complimenting himself