Web接口輸出方式選型

Web輸出方式選型

DTO

DTO分層

必要性

DTO與PO的不對稱關系決定了二者不能互相代替

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

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

PO是與數(shù)據庫直接交互的對象,比如我要在頁面上顯示數(shù)據庫中的數(shù)據,如果用PO來實現(xiàn)那么這個PO就得一直保持與數(shù)據庫的連接,直到數(shù)據顯示到頁面上來。這樣如果在service層有復雜運算的話,那么數(shù)據庫方面的延時是非??捎^的,而如果轉換成DTO之后,數(shù)據庫連接就可以盡快釋放。所以從性能上來說應該使用DTO--當然對于性能不是很苛刻的情況下不用DTO也行

缺點

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

模板語言(Mustache為例)

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

Mustache 的模板語法很簡單

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

對比DTO

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

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相當于一個數(shù)據庫,它有很多GraphQLFieldDefinition組成,F(xiàn)ield相當于數(shù)據庫表/視圖,每個表/視圖又由名稱、查詢參數(shù)、數(shù)據結構、數(shù)據組成.

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

private GraphQLOutputType userType;

private void initOutputType() {
      /**
       * 會員對象結構
       */
      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ù)據結構,以及數(shù)據檢索器

/**
     * 查詢單個用戶信息
     * @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ù)據來說明問題
                    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ù)據來說明問題
                    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ù),很容易就把查詢服務搭建好了,dataFetcher 里面還是調用原來的查詢接口

總結

在項目開發(fā)過程中,為了快速迭代,采用PO直接穿透到WEB層輸出,遇到一些懶加載穿透過多,JSON遞歸輸出的問題。DTO有它客觀存在性,但與PO共存也會導致冗余字段過多,基本上是PO加一個字段,DTO也必須加一個字段,維護起來也麻煩;模板語言會從根本上消除了懶加載的問題,但模板文件也會多起來;GraphQL可以認為是在模板的基礎上,增加了查詢的靈活度,是值得引入的框架。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容