rpc請求&響應引數規範
§0. 有話先說
0.1 幾個概念
api-應用程式介面,最狹隘的定義,指的是我們程式裡類或介面的方法。
rpc-區域網內部跨應用通訊框架。常見的有dubbo、thrift、HSF、Feign。
rpcapi-基本上可以跟程式內部api一樣用法的api。
0.2 someword:
下面說的引數,包括請求引數和返回引數。
- 方法的引數,通常不建議使用Object、Map,不易讀。
- 方法的引數,如果都用String,比如JSON字串或其他拼裝起來的字串,不易讀是一方面,同時,潛在問題更多,你品。
- rpcapi,建議自行處理異常,別拋給呼叫方。
- 方法的引數,使用列舉會提高可讀性@see。
- 。。。
0.3 為什麼有本文?
書接前文,rpcapi雖然可以像程式內部api一樣,支援java各種資料型別,可以像呼叫程式內部api一樣呼叫rpcapi。不過,所不同的是,rpcapi應該處理各種可能的異常情況,而不是丟擲異常。這使得rpcapi與restapi一樣,不能只是返回資料(資源),而應該對可能出現的異常情況進行判斷,比如引數合法性,資料是否存在,資料狀態,程式異常,等等,當沒有異常情況時,才返回所需資料(資源)。因此,就有了本文下文說的Result<T>。
§1.請求引數
1.1 如果引數比較少,比如少於3個,可以顯式定義出來。比如
getEnterpriseById(String enterpriseId)
getEnterpriseById(String enterpriseId,ProductEnum product)
1.2 如果引數超過3個,建議定義一個DTO。rpc傳輸物件暫定統一命名為DTO。請求dto命名建議以ReqDTO結尾。當然,如果請求響應都使用相同的dto的話,就直接以DTO結尾也未嘗不可。比如
addEnterprise(EnterpriseDTO enterprise);
selectEnterprise(EnterpriseDTO enterprise);
§2. 響應引數
2.1 首先,響應引數統一使用Result<T>。即,返回值統一使用泛型。Result<T>主要成員有3個:
- int code - 響應碼,成功統一是200. 對應的code列舉定義在ResultCodeEnum.java裡
- String msg - 響應描述,尤其是當非200的情況下,需要指出錯誤資訊。
- T result - 響應資料。一般在code=200的情況下會設定result。
Result<T>相關操作方法後文贅述。
2.2 其次,對於返回資料來說,同1.1、1.2所述。
2.2.1 如果比較單一,比如就返回一個交易量,可以是Result<Integer> selectTransCount(...)。
2.2.2 如果返回資料比較複雜,可以定義一個DTO,響應dto命名建議以RespDTO結尾。當然,如果請求響應都使用相同的dto的話,就直接以DTO結尾也未嘗不可。
2.2.2.1 如果你不願意定義一個dto物件,也行。
可以考慮返回JSON物件(JSON物件哦,不是JSON字串),即Result<JSONObject>。
也可以考慮返回Map物件,比如Result<Map<String,Object>>。瞧瞧,使用map,有些情況下,就不可避免的涉及到Object。使用Object來傳參或作為方法返回值是大忌,與CV大法一樣多少都會令人詬病。
2.2.3 對於返回集合的情況,當然也很簡單,無非就是Result<List<EnterpriseDTO>>了唄。
§3. 強大的Result<T>
Result<T>是一個泛型類,ResultCodeEnum定義了code的列舉項,它們定義在com.emax.zhenghe:zhenghe-rpcapistyle包裡。
maven dependency依賴:
< dependency >
< groupId >com.emax.zhenghe</ groupId >
< artifactId >zhenghe-rpcapistyle</ artifactId >
< version >1.0.1-SNAPSHOT</ version >
</ dependency >
|
Result<T>重要成員方法:
Result<T>主要操作方法是設定返回結果的(這不是廢話嘛~),返回分兩種,成功的返回,錯誤的返回。因此,Result<T>定義了兩類方法,public static Result<T> success(...)和public static Result<T> err(...)。 當然,為了方便大家使用,方法過載是免不了的。詳細見下面列表,總有一款適合你!
▄︻┻┳═一 public static <T> Result<T> success()
▄︻┻┳═一 public static <T> Result<T> success(T data)
▄︻┻┳═一 public static <T> Result<T> success(T data, String msg)
▄︻┻┳═一 public Result<T> successWithMsg(String message)
▄︻┻┳═一 public static <T> Result<T> err(String msg)
▄︻┻┳═一 public static <T> Result<T> err(ResultCodeEnum code, String msg)
▄︻┻┳═一 public static <T> Result<T> err(int code, String msg)
這個class的實現程式碼呢, 一睹芳容吧!
public class Result<T> implements Serializable { /** * 返回程式碼 */ private int code = 0; /** * 返回處理訊息 */ private String message = "操作成功!"; /** * 返回資料物件 data */ private T result; // 服務提供方的操作方法-----開始 public static <T> Result<T> success(T data){ return success(data, "成功"); } public static <T> Result<T> success(T data, String msg) { Result<T> r = new Result<>(); r.setSuccess(true); r.setCode(CommonConstant.SC_OK_200); r.setResult(data); return r; } public static <T> Result<T> success() { Result<T> r = new Result<>(); r.setSuccess(true); r.setCode(CommonConstant.SC_OK_200); r.setMessage("成功"); return r; } public Result<T> successWithMsg(String message){ this.message = message; this.code = CommonConstant.SC_OK_200; this.success = true; return this; } public static <T> Result<T> err(String msg) { return err(ResultCodeEnum.INTERNAL_SERVER_ERROR, msg); } public static <T> Result<T> err(ResultCodeEnum code, String msg) { return err(code.getCode(), StringUtils.isBlank(msg) ? code.getMsg() : msg); } public static <T> Result<T> err(int code, String msg) { Result<T> r = new Result<>(); r.setCode(code); r.setMessage(msg); r.setSuccess(false); return r; } // 服務提供方的操作方法-----結束 // 服務提供方的操作方法-----開始 // 客戶端接收到響應後,可以使用isSuccess來判斷是否成功,成功後,可以獲取返回資料進行後續處理;不成功,則可以 getCode()和 getMessage()來記錄code和msg。 public boolean isSuccess() { return code == ResultCodeEnum.SUCCESS.getCode(); } public int getCode() { return code; } public String getMessage() { return message; } public T getResult() { return this.result; } // 服務提供方的操作方法-----結束 }
對擴充套件開放
注意到了嗎?Result的code是int型別,不是上文提到的ResultCodeEnum列舉。
why?code定義成ResultCodeEnum當然是再好不過了,程式使用列舉要比數字易讀多了。為什麼不這麼做呢?——為了考慮擴充套件。試想,如果現有ResultCodeEnum的列舉項不滿足你的專案需要,你是不是要增加ResultCodeEnum列舉項?是的,那麼,這時,就要去修改zhenghe-rpcapistyle的原始碼,而zhenghe-rpcapistyle在另一個專案裡。隨著依賴zhenghe-rpcapistyle的專案的逐漸增多,ResultCodeEnum也許將變得尤其難於使用。
因此,如果zhenghe-rpcapistyle裡ResultCodeEnum不滿足專案需要,大家可以在你的專案裡自行定義一個ResultCodeEnum.java,或者可以在Constant裡定義code(推薦前者)。
§4. How to use?
如下testcase在zhenghe-rpcapistyle包裡,可以幫助你快速瞭解並掌控Result<T>。
package com.emax.zhenghe.common.api.vo;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class ResultTest {
public static void main(String[] args) {
//Result裡儲存Long型數值
Result<Long> longResult = Result.success(5L, "" );
System.out.println( "longResult=" + longResult);
System.out.println(longResult.getCode() + "----" );
longResult.setResult(1L);
System.out.println(longResult.getCode());
//Result裡儲存資料集合
Result<List<Integer>> listResult = Result.success(Arrays.asList( 1 , 2 , 4 ));
System.out.println(JSON.toJSONString(listResult.getResult()));
//如下Result相當於Result<Object>或Result<?>
Result objectResult = Result.success();
System.out.println( "objectResult.getCode()=" + objectResult.getCode());
//在某些error情況下,Result要設定響應資料。
Result<Integer> integerResult = Result.err( "sdfsda" );
integerResult.setResult( 1 );
System.out.println( "integerResult=" + integerResult);
//構造器初始化的Result物件,code的預設值是200(成功)
Result<Integer> newResult = new Result<>();
newResult.setCode( 404 );
newResult.setResult(Integer.MIN_VALUE);
System.out.println(newResult.getResult() + "---" + newResult.getCode());
}
}
|
下面,以一個簡單的示例來介紹rpc服務的實現類裡如何使用Result<T>
@Override
public Result<Long> separateFeeQuery() {
Long count=0L; //todo:呼叫service讀庫
return Result.success(count);
}
|
又例如:
@Override
public Result addEnterprise(EnterpriseDTO enterprise) {
....
boolean ok = enterpriseService.save(po);
if (ok){
return Result.success();
} else {
return Result.err( "資料儲存失敗" );
}
}
|
§5. 命名規範
方法命名可能還真不好統一,也不好立規範。
同樣rpcapi介面類名也是如此,比如可以統一以-Api結尾,也可以統一以-Service結尾,不過最好與工程裡的Service區分開來,這樣便於程式理解。
就像上面§1提到的資料傳輸物件用DTO命名一樣,有的人說用VO比較好,仁者見仁智者見智吧。