我爲何既支持又反對接口用Map來傳輸數據?

1 拋磚引玉

先來看一段十分基礎的業務代碼前端

Map<String, Object> map = service.getDataByName("悟空GoKu");
  Long userId = (Long)map.get("userId");
  String phone = (String)map.get("phone");
複製代碼

每次我寫這種map獲取返回數據老是感受十分別扭web

  1. map就像個無底洞,你不看服務提供方代碼的話就不知道里面到底 放了什麼key
  2. 拿到數據以後都要本身 強轉一下,有點麻煩。
  3. 這玩意有潛在的 類型轉換異常發生

2 請求參數Request

首先要確定的是用map來傳輸參數(前端http請求後端接口)真的是方便,不須要額外去定義一個類,想往裏面塞什麼數據就塞什麼,就以下面的例子,不須要爲每一個接口都定義一個RequestVo類,統一map接收數據庫

@PostMapping(value = "/map")
public ApiResponse testMap(@RequestBody Map<String, Object> map) {
    //獲取map中的數據
    Long userId = (Long)map.get("userId");
    String phone = (String)map.get("phone");
    //業務代碼...
    return ApiResponse.ok();
}
複製代碼

上面說了我不喜歡這樣獲取數據,但也不喜歡定義一個類來接收,由於這樣會形成類數量激增,可能一個請求接口就得對應建立一個請求類。編程

有人說不定義成類,有些額外的功能就沒法使用:json

  1. swagger這種文檔註解沒法完美兼容。

不想用swagger,代碼侵入性太強,樣式、目錄展現也通常,接口文檔推薦開源yapi後端

swagger有時確實方便,增長參數時,代碼跟文檔同時更新,沒必要額外維護文檔,但我仍是不喜歡將文檔跟代碼耦合在一塊兒。api

  1. validator驗證註解沒法使用。

驗證邏輯我仍是喜歡寫在controller,邏輯更清晰。緩存

原來的註解不必定適合某些複雜驗證,那豈不是要自定義註解,又回到了類激增的問題app

說回了map,map.get(key)這樣獲取數據確實彆扭,但咱們能夠封裝請求啊, 例如上一篇文章<<當技術leader說要把接口設計成RESTful,我拒絕了>>提到的ApiRequest。編輯器

public class ApiRequest implements Serializable {
  //....省略部分代碼    
  private Map<String, Object> data;  //經過攔截器處理後請求參數已存放在這裏
  public Long getDataParamAsLong(String name, Long defaultValue) {
      Long i = defaultValue;
      try{
          i = StringUtils.isNotEmpty(getDataParamAsString(name)) ? 
              Long.valueOf(getDataParamAsString(name)) : defaultValue;
      }catch (Exception e){
          e.printStackTrace();
      }
      return i;
  }
}
複製代碼
@PostMapping(value = "/test")
public ApiResponse test(ApiRequest apiRequest) {
    Long userId = apiRequest.getDataParamAsLong("userId", 0L);
    //省略部分代碼....
    ApiResponse response = ApiResponse.ok()
    return response;
}
複製代碼

這裏已是面向json編程了,而不是以往的面向對象。

3 響應參數Response

對於返回數據,我通常會在controller層拿到service的數據後再根據業務需求來處理數據(結構修改,數據整合),最終用map整合再response,給前端一個合適的結構, 而不是數據庫查到什麼就整個類對象返回

當前有些返回數據我也會定義一個類ReponseVo封裝,最後return給前端

你這先後矛盾啊,以前還說不要面向對象。

這裏主要考慮的有些場景下,多個接口返回的數據徹底同樣,能夠共用。 對於部分app開發者來講,他們會依賴後臺的接口來定義本身的model,類似的數據會要求後臺返回的字段命名和結構同樣,以便他們能共用model。

這.......其實他們能夠本身定義屬於他們的model,而不是徹底依賴後臺字段命名。

4 service層數據傳輸

前面說的是前端/移動端http請求咱們的網關接口,這裏是說咱們服務端之間的遠程方法調用/本地方法調用,也能夠理解成service層方法調用。若是是多個參數的話,須要封裝一個DTO,這裏最好不用map。

  1. 這種接口咱們不可能去寫一份文檔來維護。
  2. 數據反序列化問題(劃重點)

若是某個方法內部使用了緩存,且經過json反序列化後才返回,容易引起調用方發生異常

//set 
@PostMapping(value = "/setData")
public ApiResponse setData(ApiRequest request) {
    //省略部分代碼...
    Map<String, Object> map = new HashMap<>();
    map.put("id", 123L);
    map.put("name", "悟空GoKu");
    stringRedisTemplate.set("KEY_GOKU", JsonUtil.toJsonString(map));
    return ApiResponse.ok();
}
//get 
@PostMapping(value = "/getData")
public ApiResponse getData(ApiRequest request) {
    Map<String, Object> map = stringRedisTemplate.get("KEY_GOKU", Map.class);
    Long id = (Long)map.get("id");  //會發生異常ClassCastException
    return ApiResponse.ok();
}
複製代碼

json 反序列化 map 時若是原來的整數值小於 int 最大值,反序列化後本來爲 Long 類型的字段,會變爲 Integer 類型

json 序列化的優點在於可讀性更強。但沒有攜帶類型信息,只有提供了準確的類型信息才能準確地進行反序列化,這點也特別容易引起線上問題。

5 總結

最後來幾句總結闡述下本文的觀點,僅表明我的見解

  1. 前端/移動端請求接口,面向json編程,用map來傳輸數據,部分接口的返回數據可定義成VO類
  2. 服務端之間的方法調用,多個參數的話須要定義DTO類來傳輸數據。
  3. 先後端聯調,有一份清晰可見的接口文檔極爲重要,寫好代碼的同時不要忘記也要寫好文檔。

特別想diss那些字段不寫註釋、代碼加了字段又不一樣步到文檔的後端開發者hhh

相關文章
相關標籤/搜索