1. 程式人生 > 實用技巧 >rpc請求&響應引數規範

rpc請求&響應引數規範

§0. 有話先說

0.1 幾個概念

api-應用程式介面,最狹隘的定義,指的是我們程式裡類或介面的方法。

rpc-區域網內部跨應用通訊框架。常見的有dubbo、thrift、HSF、Feign。

rpcapi-基本上可以跟程式內部api一樣用法的api。

0.2 someword:

下面說的引數,包括請求引數和返回引數。

  1. 方法的引數,通常不建議使用Object、Map,不易讀。
  2. 方法的引數,如果都用String,比如JSON字串或其他拼裝起來的字串,不易讀是一方面,同時,潛在問題更多,你品。
  3. rpcapi,建議自行處理異常,別拋給呼叫方。
  4. 方法的引數,使用列舉會提高可讀性@see
  5. 。。。

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>。

packagecom.emax.zhenghe.common.api.vo; importcom.alibaba.fastjson.JSON; importlombok.extern.slf4j.Slf4j; importjava.util.Arrays; importjava.util.List; @Slf4j publicclassResultTest { publicstaticvoidmain(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 =newResult<>(); newResult.setCode(404); newResult.setResult(Integer.MIN_VALUE); System.out.println(newResult.getResult() +"---"+ newResult.getCode()); } }

下面,以一個簡單的示例來介紹rpc服務的實現類裡如何使用Result<T>

@Override publicResult<Long> separateFeeQuery() { Long count=0L;//todo:呼叫service讀庫 returnResult.success(count); }

又例如:

@Override publicResult addEnterprise(EnterpriseDTO enterprise) { .... booleanok = enterpriseService.save(po); if(ok){ returnResult.success(); }else{ returnResult.err("資料儲存失敗"); } }

§5. 命名規範

方法命名可能還真不好統一,也不好立規範。

同樣rpcapi介面類名也是如此,比如可以統一以-Api結尾,也可以統一以-Service結尾,不過最好與工程裡的Service區分開來,這樣便於程式理解。

就像上面§1提到的資料傳輸物件用DTO命名一樣,有的人說用VO比較好,仁者見仁智者見智吧。