Web输出方式选型
DTO
DTO分层必要性
DTO与PO的不对称关系决定了二者不能互相代替
DTO与PO存在在映射关系,可能是多对一,也可能是一对多,最特殊的关系就是上面大家说的这种情况“一对一”。也就是在“一对一”的情况下可以实现DTO与PO的混用,而其他情况下,如果混用都需要PO进行冗余设计,考虑这些冗余设计会比直接的、简单的造一个新的DTO出现要耗费更多的脑细胞,同时这个东西又不利于后期维护,可以说“牵一发,动从上到下”。但在项目初期,业务模型不确定的情况下,直接PO穿透到WEB层,能快速迭代出功能
性能上决定了PO代替DTO是个蹩脚的设计
PO是与数据库直接交互的对象,比如我要在页面上显示数据库中的数据,如果用PO来实现那么这个PO就得一直保持与数据库的连接,直到数据显示到页面上来。这样如果在service层有复杂运算的话,那么数据库方面的延时是非常可观的,而如果转换成DTO之后,数据库连接就可以尽快释放。所以从性能上来说应该使用DTO--当然对于性能不是很苛刻的情况下不用DTO也行
缺点
- 最极端的情况下,每个接口输出都配套有一个DTO,那么长期维护是很痛苦的
- 程序员很习惯使用
bean copy
的方式将PO的数据拷贝到DTO上,如果PO增加了字段,而DTO忘记补充,是不会出现报错的
模板语言(Mustache为例)
使用模板输出JSON数据
教程地址
Mustache 的模板语法很简单
{{keyName}}
{{#keyName}} {{/keyName}}
{{^keyName}} {{/keyName}}
{{.}}
{{<partials}}
{{{keyName}}}
{{!comments}}
对比DTO
- 最极端的情况下,每个接口输出都配套有一个模板,在java开发中,丧失了类的继承,编译校验等特性,应该比DTO还更痛苦
- DTO通常会把PO引用过去,PO是持久层,在json输出时会触发如hibernate的延迟加载,导致
递归输出
等问题,反而模板语言就没有此烦恼
GraphQL
** 一个GraphQL查询可以包含一个或者多个操作(operation),类似于一个RESTful API。操作(operation)可以使两种类型:查询(Query)或者修改(mutation)。
query {
client(id: 1) {
id
name
}
}
注意上面的例子有三个不同的部分组成:
client是查询的operation
(id: 1)包含了传入给Query的参数
查询包含id和name字段,这些字段也是我们希望查询可以返回的
{
"data": {
"client": {
"id": "1",
"name": "Uncle Charlie"
}
}
}
graphql-java
Schema相当于一个数据库,它有很多GraphQLFieldDefinition组成,Field相当于数据库表/视图,每个表/视图又由名称、查询参数、数据结构、数据组成.
1) 先定义一个数据结构(GraphQLOutputType)字段,然后定义一个初始化方法
定义一个user的规格字段,此类可以考虑使用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)再定义两个表/视图,它包括名称,查询参数,数据结构,以及数据检索器
/**
* 查询单个用户信息
* @return
*/
private GraphQLFieldDefinition createUserField() {
return GraphQLFieldDefinition.newFieldDefinition()
.name("user")
.argument(newArgument().name("id").type(GraphQLInt).build())
.type(userType)
.dataFetcher(environment -> {
// 获取查询参数
int id = environment.getArgument("id");
// 执行查询, 这里随便用一些测试数据来说明问题
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 -> {
// 获取查询参数
int page = environment.getArgument("page");
int size = environment.getArgument("size");
String name = environment.getArgument("name");
// 执行查询, 这里随便用一些测试数据来说明问题
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参数,很容易就把查询服务搭建好了,dataFetcher 里面还是调用原来的查询接口
总结
在项目开发过程中,为了快速迭代,采用PO直接穿透到WEB层输出,遇到一些懒加载穿透过多
,JSON递归输出
的问题。DTO有它客观存在性,但与PO共存也会导致冗余字段过多,基本上是PO加一个字段,DTO也必须加一个字段,维护起来也麻烦;模板语言会从根本上消除了懒加载的问题,但模板文件也会多起来;GraphQL可以认为是在模板的基础上,增加了查询的灵活度,是值得引入的框架。
网友评论