【高易用性】【高可扩展性】【高可读性】统一返回体【抽象设计】

11 篇文章 1 订阅
3 篇文章 0 订阅

【统一返回体】抽象设计

背景

在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架与前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。[1]1

维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。 [1]1

统一返回体是什么

统一返回体是一个包含了三个部分的数据 [2]2

  • code - 由后端统一定义各种返回结果的状态码
  • data - 本次返回的数据
  • message - 本次接口调用的结果描述

其中,codemessage 用于对后端处理的详情进行描述。

例如,前端发起 GET localhost:8080/the_most_handsome_person 请求(HTTP Request),后端返回以下响应体(HTTP Response Body):

{
    code: 0,
    data: {
        name: "张三",
        age: "20"
    },
    message: "请求成功"
}

统一返回体的作用

统一返回体是后端与前端开发人员进行约定的一个统一的返回格式

  • 有助于规范后端响应格式
  • 有助于前端处理请求响应

工程现状

Result<T>

当今工程实践中,统一返回体一般使用下方所示类进行建模:

  • 使用范型 <T> 进行数据类型抽象
  • 实现 Serializable 接口并为 serialVersionUID 设置固定值以实现序列化
  • 将构造器设计为 private 私有构造器,只能使用静态方法 Result.success(...)Result.error(…) 进行对象的创建
public class Result<T> implements Serializable {
    private static final Long serialVersionUID = 9192910608408209894L;

    private final T data;
    private final Integer code;
    private final String message;

    private Result(T data, Integer code, String message) {
        this.data = data;
        this.code = code;
        this.message = message;
    }

    public static <T> Result<T> success(T data, int code, String message) {
        return new Result<>(data, code, message);
    }

    public static <T> Result<T> error(T data, int code, String message) {
        return new Result<>(data, code, message);
    }
}

可以使用以下代码进行统一返回体的创建

class Person {
    private String name;
    private Integer age;
    
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    // 省略构造器、getter/setter、toString 等方法
}

@RestController
public class TestController {
    @GetMapping("/the_most_handsome_person")
 	public Result<?> theMostHandsomePerson() {
        Person zhangsan = new Person("张三", 20);
        return Result.success(zhangsan, 0, "请求成功");  /*  创建 Result<T> 对象  */
    }   
}

创建返回描述枚举类 ResultEnum

ResultEnum 枚举类有 codemessage 两个域字段,分别对应 Result 中的 codemessage 字段:

public enum ResultEnum {
    SUCCESS(0, "Success"), // 请求成功
    ERROR(1, "Error"); // 请求失败

    private final Integer code;

    private final String message;

    DefaultResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() { ... }
}

此时,后端代码无需进行硬编码,可以使用枚举类修改为:

class Person {
    private String name;
    private Integer age;
    
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    // 省略构造器、getter/setter、toString 等方法
}

@RestController
public class TestController {
    @GetMapping("/the_most_handsome_person")
 	public Result<?> theMostHandsomePerson() {
        Person zhangsan = new Person("张三", 20);
        return Result.success(zhangsan, ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage());  /*  创建 Result<T> 对象  */
    }   
}

传统方法的缺点(问题)

在实际开发中,Result 类更多地是被放在共用的工具库中。

然而,每个不同的业务可能都有自己的状态码,甚至每个接口都可能会有(根据自己的设计),而每次调用 Result.success(...) 都会造成一段极为冗长的代码

最终解决方案 —— 抽象

抽取出 ResultEnumerable 接口

观察到每次代码调用都要调用枚举类型的 getCode()getMessage() 方法(例如 return Result.success(zhangsan, ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage());),故可以将这两个方法抽离成接口(extract interface

public interface ResultEnumerable {
    Integer getCode();

    String getMessage();

    @Override
    String toString();  /*  可选  */
}

返回枚举类 ResultEnum 实现 ResultEnumerable 接口

public enum ResultEnum implements ResultEnumerable {
    SUCCESS(0, "Success"), // 请求成功
    ERROR(1, "Error"); // 请求失败

    private final Integer code;

    private final String message;

    DefaultResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "ResultEnum{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

Result 类增加多态方法

public class Result<T> implements Serializable {
    private static final Long serialVersionUID = 9192910608408209894L;
    
    private final T data;
    private final Integer code;
    private final String message;

    private Result(T data, Integer code, String message) {
        this.data = data;
        this.code = code;
        this.message = message;
    }

    // 增加方法,参数类型为 ResultEnumerable 接口
    public static <T> Result<T> success(T data, ResultEnumerable resultEnum) {
        return new Result<>(data, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> success(T data, int code, String message) {
        return new Result<>(data, code, message);
    }
        
    // 增加方法,参数类型为 ResultEnumerable 接口
    public static <T> Result<T> error(T data, ResultEnumerable resultEnum) {
        return new Result<>(data, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> error(T data, int code, String message) {
        return new Result<>(data, code, message);
    }
}

调用 Result.success(T, ResultEnumerable) 方法

此时,不需要再调用 Result.success(zhangsan, ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage());,只需要调用 Result.success(zhangsan, ResultEnum.SUCCESS) 即可:

class Person {
    private String name;
    private Integer age;
    
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    // 省略构造器、getter/setter、toString 等方法
}

@RestController
public class TestController {
    @GetMapping("/the_most_handsome_person")
 	public Result<?> theMostHandsomePerson() {
        Person zhangsan = new Person("张三", 20);
        
        return Result.success(zhangsan, ResultEnum.SUCCESS); // 使用枚举即可
    }   
}

新实现的优点

易用性极高

在实际开发中,只需要将 Result 类与 ResultEnumerable 接口放在自己的项目代码中即可,无其他依赖。

可扩展性极高

在自己的实际业务中,只需要实现 ResultEnumerable 接口,就可以实现自己的枚举类!

public enum UserResultEnum implements ResultEnumerable {
    SUCCESS(0, "Success"), // 请求成功
    USER_NOT_FOUND(1, "User not found"), // 没有该用户
    USER_INFO_IS_PRIVATE(2, "User's information is private"); // 用户信息是隐私

    private final Integer code;

    private final String message;

    DefaultResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "ResultEnum{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

可读性极高

命名清晰的枚举可以清晰地表述实际信息(例如 USER_NOT_FOUND):

import static com.example.demo.result.UserResultEnum.*;

@RestController
public class TestController {
    @GetMapping("/the_most_handsome_person")
 	public Result<?> theMostHandsomePerson() {
        Person zhangsan = new Person("张三", 20);
        
        // 代码内容:Result.success(zhangsan, SUCCESS)
        // 实际含义: 返回    成功     张三       成功
        // return Result.success(zhangsan, SUCCESS);
        
        // 代码内容:Result.error(null, USER_NOT_FOUND)
        // 实际含义: 返回    失败          没有该用户
        return Result.error(null, USER_NOT_FOUND);
    }   
}

完整的代码实现

Result

package cn.tzq0301.result;

import java.io.Serializable;

import static cn.tzq0301.result.DefaultResultEnum.*;

/**
 * @author tzq0301
 * @version 1.0
 */
public class Result<T> implements Serializable {

    private static final Long serialVersionUID = 9192910608408209894L;

    private final T data;

    private final Integer code;

    private final String message;

    private Result(T data, Integer code, String message) {
        this.data = data;
        this.code = code;
        this.message = message;
    }

    public static <T> Result<T> success() {
        return new Result<>(null, SUCCESS.getCode(), SUCCESS.getMessage());
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(data, SUCCESS.getCode(), SUCCESS.getMessage());
    }

    public static <T> Result<T> success(int code, String message) {
        return new Result<>(null, code, message);
    }

    public static <T> Result<T> success(ResultEnumerable resultEnum) {
        return new Result<>(null, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> success(T data, ResultEnumerable resultEnum) {
        return new Result<>(data, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> success(T data, int code, String message) {
        return new Result<>(data, code, message);
    }

    public static <T> Result<T> error() {
        return new Result<>(null, ERROR.getCode(), ERROR.getMessage());
    }

    public static <T> Result<T> error(T data) {
        return new Result<>(data, ERROR.getCode(), ERROR.getMessage());
    }

    public static <T> Result<T> error(int code, String message) {
        return new Result<>(null, code, message);
    }

    public static <T> Result<T> error(ResultEnumerable resultEnum) {
        return new Result<>(null, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> error(T data, ResultEnumerable resultEnum) {
        return new Result<>(data, resultEnum.getCode(), resultEnum.getMessage());
    }

    public static <T> Result<T> error(T data, int code, String message) {
        return new Result<>(data, code, message);
    }

    public T getData() {
        return data;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "Result{" +
                "data=" + data +
                ", code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

ResultEnumerable 接口

package cn.tzq0301.result;

/**
 * @author tzq0301
 * @version 1.0
 */
public interface ResultEnumerable {
    Integer getCode();

    String getMessage();

    @Override
    String toString();
}

DefaultResultEnum

package cn.tzq0301.result;

/**
 * {@link Result} 的返回码与返回信息的枚举类
 *
 * @author tzq0301
 * @version 1.0
 */
public enum DefaultResultEnum implements ResultEnumerable {
    SUCCESS(0, "Success"), // 请求成功
    ERROR(1, "Error"); // 请求失败

    private final Integer code;

    private final String message;

    DefaultResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "ResultEnum{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

参考资料

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值