Web輸出方式選型
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ǔ)上缴渊,增加了查詢的靈活度赏壹,是值得引入的框架。