微服务架构下,响应格式的规范化

背景

前后端分离的开发模式已成为业界的事实标准:后端开发服务接口,提供业务需要的数据;前端拿到数据后,执行数据渲染的逻辑。
随着微服务架构的普及,大量的巨石(单体)应用,按照不同的领域,拆分成很多的领域集群,每个领域由不同的开发团队来负责,最大化产品迭代的速度,践行cicd,devops。

多个领域团队产出的服务数据,在格式上不统一,给前端同学的解析带来了很大的困扰;最普遍的场景是,分别解析异构的数据格式,给代码的整洁规范带来了很大的挑战。同时,后端服务在抛出异常时,跟前端的交互,展示也因为没有规范,会导致大量的无意义劳动。

混乱的现状
image.png

愿景与期望

格式规范化,并预留必要的扩展性

  1. 对于不同的领域团队,透传出的数据格式在系统层面做到一致。
  2. 对于正常的业务请求,和异常发生时,在系统层面也做到一致。
  3. 在服务接口格式基本一致的基础上,留出必要的扩展性
    a. 让用户自定义,是否需要输出系统统一的格式,预留出个性化结果的扩展点(比如:检查系统是否健康的结果响应(return 200;)已然存在)
    b. 输出格式的扩展性,以哪种格式来输出.

规范化之后的模型。
image.png

解决规划

返回格式的定义(ResultDTO)


@Data

public class ResultDTO {

    /**

     * 是否成功

     */

    private Boolean success;

    /**

     * 返回的对象

     */

    private Object result;

    /**

     * 错误码

     */

    private String errorCode;

    /**

     * 错误信息

     */

    private String errorMsg;



    public ResultDTO() {

    }



    public ResultDTO(Boolean success) {

        this.success = success;

    }



    public ResultDTO(String code, String msg) {

        this.success = false;

        this.errorCode = code;

        this.errorMsg = msg;

    }



    public ResultDTO(Object result) {

        this.success = true;

        this.result = result;

    }

}

正常业务响应时展示示例




{

  "result": "world",

  "success": true

}

业务异常时展示示例




{

  "errorCode": "1001",

  "errorMsg": "系统处理失败",

  "success": false

}

正常业务响应流程处理

两个思路

  1. 添加ResponseBodyAdvice(推荐 @since spring 4.1)

  2. 添加 ReturnValueHandler

业务异常时统一处理

ControllerAdvice

落地方案

自定义annotation(WithApiResult),支持类级别和方法级别的格式输出


@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface WithApiResult {



  Class<ResultDTO> result() default ResultDTO.class;

}

实现自定义的ResponseBodyAdvice


@Slf4j

@ControllerAdvice

public class GlobalControllerAdvice implements ResponseBodyAdvice {

  @Override

  public boolean supports(MethodParameter returnType, Class converterType) {

    return retrieveWithApiResultAnnotation(returnType) != null;

  }



  private WithApiResult retrieveWithApiResultAnnotation(MethodParameter returnType) {

    Class<?> declaringClass = returnType.getDeclaringClass();

    WithApiResult annotation = AnnotationUtils.findAnnotation(declaringClass, WithApiResult.class);

    if (annotation == null) {

      annotation = returnType.getMethodAnnotation(WithApiResult.class);

    }

    return annotation;

  }



  @Override

  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

    if (body instanceof ResultDTO) {

      return body;

    }



    ResultDTO wrapper = new ResultDTO();

    wrapper.setResult(body);

    wrapper.setSuccess(true);

    return wrapper;

  }

}

或者:实现自定义的HandlerMethodReturnValueHandler并加载进springmvc(ResponseBodyAdvice两种方案选一即可)




public class WithApiResultMethodProcessor implements HandlerMethodReturnValueHandler {

    private RequestResponseBodyMethodProcessor target;





    public WithApiResultMethodProcessor(RequestResponseBodyMethodProcessor target) {

        this.target = target;

    }



    @Override

    public boolean supportsReturnType(MethodParameter returnType) {

        return target.supportsReturnType(returnType);

    }



    @Override

    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        WithApiResult annotation = retrieveWithApiResultAnnotation(returnType);



        if (annotation != null) {

            Class<ResultDTO> result = annotation.result();

            ResultDTO wrapper = result.newInstance();

            wrapper.setResult(returnValue);

            wrapper.setSuccess(true);



            returnValue = wrapper;

        }



        target.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

    }



    private WithApiResult retrieveWithApiResultAnnotation(MethodParameter returnType) {

        Class<?> declaringClass = returnType.getDeclaringClass();

        WithApiResult annotation = AnnotationUtils.findAnnotation(declaringClass, WithApiResult.class);

        if (annotation == null) {

            annotation = returnType.getMethodAnnotation(WithApiResult.class);

        }

        return annotation;

    }

}

自定义的ReturnValueHandler加载到springmvc的执行流程


	@Autowired

    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;



    @PostConstruct

    public void postConstruct() {

        List<HandlerMethodReturnValueHandler> original = requestMappingHandlerAdapter.getReturnValueHandlers();

        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();



        for (HandlerMethodReturnValueHandler each : original) {

            if (each instanceof RequestResponseBodyMethodProcessor) {

                WithApiResultMethodProcessor wrapper = new WithApiResultMethodProcessor((RequestResponseBodyMethodProcessor) each);

                log.info("WithApiResultMethodProcessor added.");

                handlers.add(wrapper);

            }

            handlers.add(each);

        }



        requestMappingHandlerAdapter.setReturnValueHandlers(handlers);

    }

异常流程的统一处理


  @ExceptionHandler

  @ResponseBody

  public ResultDTO handleException(HttpServletRequest request, Exception ex) {

    log.error("system error", ex);

    ResultDTO result = null;



    if (ex instanceof IllegalArgumentException) {

      result = new ResultDTO(CommonErrorEnum.PARAM_ERROR.getErrorCode(), ex.getMessage());

    } else if (ex instanceof BizException) {

      BizException bizEx = (BizException) ex;

      result = new ResultDTO(bizEx.getErrorCode(), ex.getMessage());

    } else if (ex instanceof MethodArgumentNotValidException) {

      MethodArgumentNotValidException validException = (MethodArgumentNotValidException) ex;

      BindingResult bindingResult = validException.getBindingResult();

      List<ObjectError> allErrors = bindingResult.getAllErrors();



      StringBuilder builder = new StringBuilder();

      allErrors.forEach(each -> {

        builder.append(each.getDefaultMessage()).append(";");

      });

      result = new ResultDTO(CommonErrorEnum.PARAM_ERROR.getErrorCode(), builder.toString());

    } else {

      result = new ResultDTO(CommonErrorEnum.SYSTEM_ERROR.getErrorCode(), CommonErrorEnum.SYSTEM_ERROR.getErrorMsg());

    }



    return result;

  }

过程记录

  1. 添加ReturnValueHandler的时机(详细查看落地方案)

  2. org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addReturnValueHandlers的设计用意

添加的ReturnValueHandlers作为customed handlers会放到default handlers的后面

  1. 业务异常的处理,建议封装成ErrorEnum,统一处理 链接待补充。

  2. 分页结果的格式定义(PagedDTO)

对于分页查询的场景,PagedDTO赋值给result,示例如下:


{

	"result": {

		"pageNum": 1,

		"pageSize": 10,

		"totalRecord": 100,

		"list": [{}]

	},

	"success": true

}

PagedDTO模型


@Data

public class PagedDTO<T> implements Serializable {

    private static final long serialVersionUID = -3884577158754259731L;

    /**

     * 第几页

     */

    private Long pageNum = 1L;

    /**

     * 页面size

     */

    private Long pageSize = 20L;

    /**

     * count记录数

     */

    private Long totalRecord = 0L;

    /**

     * 模型列表

     */

    private List<T> list;



    public Long getTotalPage() {

        return (totalRecord - 1) / pageSize + 1;

    }

}

发布了1 篇原创文章 · 获赞 0 · 访问量 7

猜你喜欢

转载自blog.csdn.net/weixin_43493520/article/details/105744329
今日推荐