美文网首页
Web接口输出方式选型

Web接口输出方式选型

作者: nothingp | 来源:发表于2017-07-26 11:15 被阅读0次

    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可以认为是在模板的基础上,增加了查询的灵活度,是值得引入的框架。

    相关文章

      网友评论

          本文标题:Web接口输出方式选型

          本文链接:https://www.haomeiwen.com/subject/uwcmcxtx.html