【统一返回体】抽象设计
文章目录
背景
在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架与前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。[1]1
维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。 [1]1
统一返回体是什么
- 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;
}
// 省略构造器、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
枚举类有 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;
}
// 省略构造器、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 + '\'' +
'}';
}
}