【统一返回体】抽象设计
背景
在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架与前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。[1]1
维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。 [1]1
统一返回体是什么
统一返回体是一个包含了三个部分的数据 [2]2:
- code - 由后端统一定义各种返回结果的状态码
- data - 本次返回的数据
- message - 本次接口调用的结果描述
其中,code 与 message 用于对后端处理的详情进行描述。
例如,前端发起 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;
}
}
@RestController
public class TestController {
@GetMapping("/the_most_handsome_person")
public Result<?> theMostHandsomePerson() {
Person zhangsan = new Person("张三", 20);
return Result.success(zhangsan, 0, "请求成功");
}
}
创建返回描述枚举类 ResultEnum
ResultEnum 枚举类有 code 与 message 两个域字段,分别对应 Result 中的 code 与 message 字段:
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;
}
}
@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 类更多地是被放在共用的工具库中。
然而,每个不同的业务可能都有自己的状态码,甚至每个接口都可能会有(根据自己的设计),而每次调用 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;
}
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(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;
}
}
@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);
return Result.error(null, USER_NOT_FOUND);
}
}
完整的代码实现
Result 类
package cn.tzq0301.result;
import java.io.Serializable;
import static cn.tzq0301.result.DefaultResultEnum.*;
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;
public interface ResultEnumerable {
Integer getCode();
String getMessage();
@Override
String toString();
}
DefaultResultEnum 类
package cn.tzq0301.result;
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 + '\'' +
'}';
}
}
参考资料
|