§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 message - 響應描述,尤其是當非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的實現代碼呢, 一睹芳容吧!
對擴展開放
注意到了嗎?Result的code是int類型,不是上文提到的ResultCodeEnum枚舉。
why?code定義成ResultCodeEnum當然是再好不過了,程序使用枚舉要比數字易讀多了。為什么不這么做呢?——為了考慮擴展。試想,如果現有ResultCodeEnum的枚舉項不滿足你的項目需要,你是不是要增加ResultCodeEnum枚舉項?是的,那么,這時,就要去修改zhenghe-rpcapistyle的源碼,而zhenghe-rpcapistyle在另一個項目里。隨着依賴zhenghe-rpcapistyle的項目的逐漸增多,ResultCodeEnum也許將變得尤其難於使用。
因此,如果zhenghe-rpcapistyle里ResultCodeEnum不滿足項目需要,大家可以在你的項目里自行定義一個ResultCodeEnum.java,或者可以在系統的常量類里定義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比較好,仁者見仁智者見智吧。