Web接口輸出方式選型

Web輸出方式選型

DTO

DTO分層

必要性

DTO與PO的不對稱關(guān)系決定了二者不能互相代替

DTO與PO存在在映射關(guān)系钮热,可能是多對一校辩,也可能是一對多窘问,最特殊的關(guān)系就是上面大家說的這種情況“一對一”。也就是在“一對一”的情況下可以實現(xiàn)DTO與PO的混用宜咒,而其他情況下惠赫,如果混用都需要PO進行冗余設(shè)計,考慮這些冗余設(shè)計會比直接的故黑、簡單的造一個新的DTO出現(xiàn)要耗費更多的腦細胞儿咱,同時這個東西又不利于后期維護,可以說“牽一發(fā)场晶,動從上到下”混埠。但在項目初期,業(yè)務(wù)模型不確定的情況下诗轻,直接PO穿透到WEB層钳宪,能快速迭代出功能

性能上決定了PO代替DTO是個蹩腳的設(shè)計

PO是與數(shù)據(jù)庫直接交互的對象,比如我要在頁面上顯示數(shù)據(jù)庫中的數(shù)據(jù)扳炬,如果用PO來實現(xiàn)那么這個PO就得一直保持與數(shù)據(jù)庫的連接吏颖,直到數(shù)據(jù)顯示到頁面上來。這樣如果在service層有復(fù)雜運算的話恨樟,那么數(shù)據(jù)庫方面的延時是非嘲胱恚可觀的,而如果轉(zhuǎn)換成DTO之后厌杜,數(shù)據(jù)庫連接就可以盡快釋放奉呛。所以從性能上來說應(yīng)該使用DTO--當(dāng)然對于性能不是很苛刻的情況下不用DTO也行

缺點

  • 最極端的情況下,每個接口輸出都配套有一個DTO夯尽,那么長期維護是很痛苦的
  • 程序員很習(xí)慣使用bean copy的方式將PO的數(shù)據(jù)拷貝到DTO上瞧壮,如果PO增加了字段,而DTO忘記補充匙握,是不會出現(xiàn)報錯的

模板語言(Mustache為例)

使用模板輸出JSON數(shù)據(jù)
教程地址

Mustache 的模板語法很簡單

{{keyName}}
{{#keyName}} {{/keyName}}
{{^keyName}} {{/keyName}}
{{.}}
{{<partials}}
{{{keyName}}}
{{!comments}}

對比DTO

  • 最極端的情況下咆槽,每個接口輸出都配套有一個模板,在java開發(fā)中圈纺,喪失了類的繼承秦忿,編譯校驗等特性麦射,應(yīng)該比DTO還更痛苦
  • DTO通常會把PO引用過去,PO是持久層灯谣,在json輸出時會觸發(fā)如hibernate的延遲加載潜秋,導(dǎo)致遞歸輸出等問題,反而模板語言就沒有此煩惱

GraphQL

** 一個GraphQL查詢可以包含一個或者多個操作(operation)胎许,類似于一個RESTful API峻呛。操作(operation)可以使兩種類型:查詢(Query)或者修改(mutation)。

query {
  client(id: 1) {
    id 
    name
  }
}

注意上面的例子有三個不同的部分組成:
client是查詢的operation
(id: 1)包含了傳入給Query的參數(shù)
查詢包含id和name字段辜窑,這些字段也是我們希望查詢可以返回的

{
  "data": {
    "client": {
      "id": "1",
      "name": "Uncle Charlie"
    }
  }
}

graphql-java

Schema相當(dāng)于一個數(shù)據(jù)庫钩述,它有很多GraphQLFieldDefinition組成,F(xiàn)ield相當(dāng)于數(shù)據(jù)庫表/視圖穆碎,每個表/視圖又由名稱牙勘、查詢參數(shù)、數(shù)據(jù)結(jié)構(gòu)所禀、數(shù)據(jù)組成.

1) 先定義一個數(shù)據(jù)結(jié)構(gòu)(GraphQLOutputType)字段方面,然后定義一個初始化方法
定義一個user的規(guī)格字段,此類可以考慮使用lombok來幫我們生成
lombok傳送門

private GraphQLOutputType userType;

private void initOutputType() {
      /**
       * 會員對象結(jié)構(gòu)
       */
      userType = newObject()
              .name("User")
              .field(newFieldDefinition().name("id").type(GraphQLInt).build())
              .field(newFieldDefinition().name("age").type(GraphQLInt).build())
              .field(newFieldDefinition().name("sex").type(GraphQLInt).build())
              .field(newFieldDefinition().name("name").type(GraphQLString).build())
              .field(newFieldDefinition().name("pic").type(GraphQLString).build())
              .build();
}

2)再定義兩個表/視圖色徘,它包括名稱葡幸,查詢參數(shù),數(shù)據(jù)結(jié)構(gòu)贺氓,以及數(shù)據(jù)檢索器

/**
     * 查詢單個用戶信息
     * @return
     */
    private GraphQLFieldDefinition createUserField() {
        return GraphQLFieldDefinition.newFieldDefinition()
                .name("user")
                .argument(newArgument().name("id").type(GraphQLInt).build())
                .type(userType)
                .dataFetcher(environment -> {
                    // 獲取查詢參數(shù)
                    int id = environment.getArgument("id");

                    // 執(zhí)行查詢, 這里隨便用一些測試數(shù)據(jù)來說明問題
                    User user = new User();
                    user.setId(id);
                    user.setAge(id + 15);
                    user.setSex(id % 2);
                    user.setName("Name_" + id);
                    user.setPic("pic_" + id + ".jpg");
                    return user;
                })
                .build();
    }

    /**
     * 查詢多個會員信息
     * @return
     */
    private GraphQLFieldDefinition createUsersField() {
        return GraphQLFieldDefinition.newFieldDefinition()
                .name("users")
                .argument(newArgument().name("page").type(GraphQLInt).build())
                .argument(newArgument().name("size").type(GraphQLInt).build())
                .argument(newArgument().name("name").type(GraphQLString).build())
                .type(new GraphQLList(userType))
                .dataFetcher(environment -> {
                    // 獲取查詢參數(shù)
                    int page = environment.getArgument("page");
                    int size = environment.getArgument("size");
                    String name = environment.getArgument("name");

                    // 執(zhí)行查詢, 這里隨便用一些測試數(shù)據(jù)來說明問題
                    List<User> list = new ArrayList<>(size);
                    for (int i = 0; i < size; i++) {
                        User user = new User();
                        user.setId(i);
                        user.setAge(i + 15);
                        user.setSex(i % 2);
                        user.setName(name + "_" + page + "_" + i);
                        user.setPic("pic_" + i + ".jpg");
                        list.add(user);
                    }
                    return list;
                })
                .build();
    }


3)接著定義一個Schema,并將其初始化床蜘,它包含一個名稱辙培,以及一個或多個表/視圖(Field)

 private GraphQLSchema schema;

    public GraphSchema() {
        initOutputType();
        schema = GraphQLSchema.newSchema().query(newObject()
                .name("GraphQuery")
                .field(createUsersField())
                .field(createUserField())
                .build()).build();
    }

4)之后寫一個main方法,來測試一下

public static void main(String[] args) {
        GraphQLSchema schema = new GraphSchema().getSchema();

        String query1 = "{users(page:2,size:5,name:\"john\") {id,sex,name,pic}}";
        String query2 = "{user(id:6) {id,sex,name,pic}}";
        String query3 = "{user(id:6) {id,sex,name,pic},users(page:2,size:5,name:\"john\") {id,sex,name,pic}}";

        Map<String, Object> result1 = (Map<String, Object>) new GraphQL(schema).execute(query1).getData();
        Map<String, Object> result2 = (Map<String, Object>) new GraphQL(schema).execute(query2).getData();
        Map<String, Object> result3 = (Map<String, Object>) new GraphQL(schema).execute(query3).getData();

        // 查詢用戶列表
        System.out.println(result1);
        // 查詢單個用戶
        System.out.println(result2);
        // 單個用戶邢锯、跟用戶列表一起查
        System.out.println(result3);

}

5)最后把main方法里面的代碼放到web層扬蕊,只需要定義一個query參數(shù),很容易就把查詢服務(wù)搭建好了丹擎,dataFetcher 里面還是調(diào)用原來的查詢接口

總結(jié)

在項目開發(fā)過程中尾抑,為了快速迭代,采用PO直接穿透到WEB層輸出蒂培,遇到一些懶加載穿透過多再愈,JSON遞歸輸出的問題。DTO有它客觀存在性护戳,但與PO共存也會導(dǎo)致冗余字段過多翎冲,基本上是PO加一個字段,DTO也必須加一個字段媳荒,維護起來也麻煩抗悍;模板語言會從根本上消除了懶加載的問題驹饺,但模板文件也會多起來;GraphQL可以認(rèn)為是在模板的基礎(chǔ)上缴渊,增加了查詢的靈活度赏壹,是值得引入的框架。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衔沼,一起剝皮案震驚了整個濱河市蝌借,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俐巴,老刑警劉巖骨望,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異欣舵,居然都是意外死亡擎鸠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門缘圈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劣光,“玉大人,你說我怎么就攤上這事糟把【钗校” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵遣疯,是天一觀的道長雄可。 經(jīng)常有香客問我,道長缠犀,這世上最難降的妖魔是什么数苫? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮辨液,結(jié)果婚禮上虐急,老公的妹妹穿的比我還像新娘。我一直安慰自己滔迈,他們只是感情好止吁,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著燎悍,像睡著了一般敬惦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谈山,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天仁热,我揣著相機與錄音,去河邊找鬼。 笑死抗蠢,一個胖子當(dāng)著我的面吹牛举哟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迅矛,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼妨猩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秽褒?” 一聲冷哼從身側(cè)響起壶硅,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎销斟,沒想到半個月后庐椒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蚂踊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年约谈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犁钟。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡棱诱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涝动,到底是詐尸還是另有隱情迈勋,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布醋粟,位于F島的核電站靡菇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏米愿。R本人自食惡果不足惜镰官,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吗货。 院中可真熱鬧,春花似錦狈网、人聲如沸宙搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勇垛。三九已至,卻和暖如春士鸥,著一層夾襖步出監(jiān)牢的瞬間闲孤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工烤礁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讼积,地道東北人肥照。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像勤众,于是被迫代替她去往敵國和親舆绎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容