当我们谈论Java应用程序的开发时,数据传输对象(被亲切地称为DTOs)是许多讨论的主题。DDO诞生于EJB 2的Java世界,有两个目的。

首先,规避EJB序列化问题;其次,它们隐式定义一个程序集阶段,其中将用于表示的所有数据在实际进入表示层之前都会进行封送。由于 EJB 不再大规模使用,因此是否可以丢弃 DT?本文的目的是谈谈 DTO 的有用性并解决此问题。

毕竟,在几个新主题(例如,云和微服务)的环境中,此层是否有意义?当涉及到良好的软件体系结构时,答案几乎一致:这取决于您希望实体与可视化层耦合的紧密程度。

思考层中的基础架构,并本身分为三个相互关联的部分,我们有著名的MVC。

model, view, controller

值得注意的是,此策略并非仅限于 Spring MVC 和 JSF 等 Web 应用程序堆栈,使用 JSON 在宁静的应用程序中公开数据,JSON 数据用作可视化效果,即使它对典型用户不友好。

简要说明一下 MVC 后,我们将讨论使用 DTO 的优缺点。从分层应用的思维来看,DTO首先的目标是将模型与视图分开。思考 DTO 的问题:

  • 增加复杂性
  • 有可能重复代码
  • 添加新图层会影响延迟层,即可能的性能损失。

在不需要丰富模型作为前提的简单系统中,不使用 DTO 最终为应用程序带来巨大好处。有趣的是,许多序列化框架最终迫使属性具有访问器或 getter 和 setter 方法,这些方法始终作为强制性的和公开的,因此,在某些时候,这将对应用程序封装和安全性产生影响。

另一个选项是添加 DTO 层,这基本上保证了视图和模型的分离,如前所述是的,各种框架中有几个注释指示哪些字段不会显示。但是,如果您忘记写下来,可能会意外导出关键字段,例如,用户的密码。

  • 便于以对象方向绘制。干净代码明确说明对象方向的一点是 OOP 隐藏数据以公开行为,封装有助于此。

  • 便于更新数据库。通常必须重构、迁移数据库而不此更改影响客户。这种分离有助于优化,修改数据库,而不会影响可视化效果。

  • 版本控制、向后兼容性是一个重要点,尤其是在具有供公众使用的 API 和多个客户时,因此可以为每个版本配备一个 DTO,并无需担心地发展业务模式。

  • 另一个好处是,使用丰富的模型和创建一个 API 是项目符号批准的易用性。例如,在我的模型中,我可以使用货币 API;但是,在我的可视化层中,我导出为一个简单的对象,仅具有可视化的货币价值。这是Java中正确的旧字符串。

  • CQRS。是的,命令查询责任分离是否有关分离写入和读取数据的责任以及如何在没有 DTO 的情况下执行此操作?

  • 通常,添加图层意味着分离和促进维护,而牺牲了添加更多类和复杂性,因为我们还必须考虑这些层之间的转换操作。例如,这是 MVC 存在的原因,因此了解一切都基于影响和权衡,或者在特定应用程序或情况下受到伤害,这一点非常重要。

    缺少这些层是非常坏的,它可能导致一个高地模式(只有一个)有一个类的所有责任。同样,多余的层成为洋葱模式,开发人员在通过每个层时会哭。

    DTO 中更频繁的批评是执行转换。好消息是,有几个转换框架,也就是说,没有必要手动进行更改。在本文中,我们将选择一个模型映射器

    第一步是定义项目的依赖项,例如,在 Maven 中:

    Xml

     

     
    1
    <依赖>
    2
    <组 Id>org.modelmapper</组 Id>
    3
    <工件Id>模型映射器<artifactId>
    4
    </版本>

    5
    </依赖项 >
    6
    
    

    为了说明 DTO 的概念,我们将使用连接到 MongoDB 的 JAX-RS 创建应用程序,所有这些都归功于雅加达 EE,使用 Payara 作为服务器。我们使用用户名、工资、生日和用户可以讲的语言列表来管理用户。由于我们将在雅加达 EE 与 MongoDB 合作,我们将使用雅加达 NoSQL。

    Java

     

    xxxxxxx
    1
    35
    1
    nosql.映射.;

    2
    进口雅加达nosql.映射.转换;
    3
    进口雅加达nosql.映射.实体;
    4
    进口雅加达nosql.映射.ID;
    5
    导入我的公司基础设施货币货币属性转换器;
    6
    
    
    7
    导入javax货币量;
    8
    时间本地日期;

    9
    导入java乌蒂尔.集合;
    10
    导入java乌蒂尔.列表;
    11
    导入java乌蒂尔.地图;
    12
    导入java乌蒂尔.对象;
    13
    
    
    14
    @Entity
    15
    公共用户|
    16
    
    
    17
    私有字符串昵称;

    19
    
    
    20
    @Column
    21
    @Convert(货币货币转换)。
    22
    私人货币金额工资;
    23
    
    
    24
    @Column
    25
    私人列表<字符串>语言;
    26
    
    
    27
    私人本地日期生日;

    29
    
    
    30
    @Column
    31
    私人地图<字符串String>设置;
    32
    
    
    33
    只有getter
    34
    }
    35
    
    

    通常,实体具有所有属性的获取者和设置器是没有意义的;毕竟,这将是相同的,离开属性直接公共对于我们的 DTO,我们将具有实体的所有字段;但是,对于可视化,我们的"货币量"将是一个"字符串",周年日期将遵循相同的行。

    Java

     

    xxxxxxx
    1
    18
    1
    导入java乌蒂尔.列表;
    2
    乌蒂尔.地图;

    3
    
    
    4
    公共用户DTO |
    5
    
    
    6
    私人字符串昵称;
    7
    
    
    8
    私人字符串工资;
    9
    
    
    10
    私人列表<字符串>语言;
    11
    
    
    12
    私人地图<字符串字符串> 设置;

    15
    
    
    16
    //获取器和设置器
    17
    }
    18
    
    

    映射器最大的好处是,我们不必担心手动执行此操作。唯一需要注意的是,特定类型,例如货币 API 的"货币量",将需要创建一个转换,成为"String",反之亦然。

    Java

     

    xxxxxxx
    1
    32
    1
    导入组织模型映射器摘要 转换器;
    2
    
    
    3
    导入javax

    货币量;

    4
    
    
    5
    公共货币字符串转换器扩展抽象转换器<货币金额字符串> |
    6
    
    
    7
    @Override
    8
    受保护的字符串转换货币金额) |
    9
    如果=) |
    10
    返回null;
    11
    返回到弦();

    13
      }
    14
    }
    15
    
    
    16
    
    
    17
    导入组织贾瓦钱.莫内塔.;
    18
    导入组织模型映射器摘要 转换器;
    19
    
    
    20
    导入javax

    1px;"•公共字符串货币转换器扩展抽象转换器<字符串货币金额> |

    23
    
    
    24
    @Override
    25
    受保护的货币量转换字符串) |
    26
    如果=) |
    27
    返回null;
    28
      }
    29
    解析);

    30
      }
    31
    }
    32
    
    

    转换器已准备就绪;我们的下一步是实例化执行"ModelMapper"转换的类,使用依赖项注入的一个要点是我们可以将其定义为应用程序级别。从现在起,整个应用程序可以使用相同的映射器;为此,只需使用注释"注入",我们将向前看。

    Java

     

    xxxxxxx
    1
    35
     
    1
    模型映射器模型映射器;

    2
    
    
    3
    导入javax注释.后构造;
    4
    导入javax企业上下文应用程序范围;
    5
    导入javax企业注入正在生产;
    6
    导入java乌蒂尔.函数.供应商;
    7
    
    
    8
    导入静态组织

    配置.配置.访问级别私人;

    9
    
    
    10
    @ApplicationScoped
    11
    公共地图制作人实现供应商<模型映射器> |
    12
    
    
    13
    私有模型映射器映射器;
    14
    
    
    15
    @PostConstruct
    16
    这个映射器 = 模型映射器();

    18
    这个映射器获取配置()
    19
      .设置场匹配启用true
    20
      .设置场访问级别私人);
    21
    这个映射器添加转换器字符串货币货币转换器());
    22
    这个映射器加比:(货币字符串转换器());
    23
    映射器添加转换器字符串本地数据转换器());

    24
    这个映射器添加转换器新的本地日期字符串转换器());
    25
    这个映射器添加转换器用户DTO转换器());
    26
      }
    27
    
    
    28
    
    
    29
    @Override
    30
    @Produces
    31
    返回映射器;

    33
      }
    34
    }
    35
    
    

    使用 Jakarta NoSQL 的一个显著优势是易于集成数据库。例如,在本文中,我们将使用存储库的概念,从中我们将创建一个接口,雅加达 NoSQL 将为其处理此实现。

    Java

     

    xxxxxxx
    1
    11
    1
    nosql.映射.存储库;

    2
    
    
    3
    导入javax企业上下文应用程序范围;
    4
    导入java乌蒂尔.;
    5
    
    
    6
    @ApplicationScoped
    7
    公共接口用户存储库扩展存储库<用户字符串> |
    8
    }

    10
    
    
    11
    
    

    在最后一步中,我们将与 JAX-RS 发出呼吁。关键是数据公开全部由 DTO 完成;也就是说,由于 DTO,可以在客户不知情的情况下在实体内进行任何修改。如前所述,映射器被注入,"映射"方法极大地促进了 DTO 和实体之间的集成,而无需为此提供多少代码。

    Java

     

    xxxxxxx
    1
    57
     
    1
    注入注射;

    2
    导入javaxws.rs.消耗;
    3
    导入javaxws.rs.删除;
    4
    导入javaxws.rs.获取;
    5
    导入javaxws.rs.帖子;
    6
    导入javaxws.rs.路径;
    7
    导入javaxws.rs

    1px;"*导入 javaxws.rs.正在生产;

    9
    导入javaxws.rs.网络应用程序例外;
    10
    导入javaxws.rs.核心媒体类型;
    11
    导入javaxws.rs.核心回应;
    12
    导入java乌蒂尔.列表;
    13
    导入java乌蒂尔.收藏家;
    14
    乌蒂尔.;

    15
    
    
    16
    @Path"用户"
    17
    @Consumes(媒体类型)。APPLICATION_JSON
    18
    @Produces(媒体类型)。APPLICATION_JSON
    19
    公共用户资源|
    20
    
    
    21
    @Inject
    22
    @Inject

    25
    私有模型映射器映射器;
    26
    
    
    27
    @GET
    28
    公开列表<用户DTO>获取所有() |
    29
    <用户>用户=存储库查找所有();
    30
    返回用户地图(u->映射器.地图(uUserDTO)

    1px;">               .收藏收藏家)。列表());

    32
      }
    33
    
    
    34
    @POST
    35
    公共空位插入用户 DTOdto) |
    36
    用户映射=映射器地图(dto用户)。;
    37
    存储库.保存地图);
    38
    
    
    39
    @POST

    42
    @Path"id"
    43
    公共无效更新@PathParam"id"字符串id用户DTOdto) |
    44
    用户user=存储库findByIdID).或ElseThrow->
    45
    新的Web 应用程序例外响应.状态.NOT_FOUND));
    46
    用户映射=映射器地图(dto用户)。;
    47
    更新地图);

    48
    存储库.保存地图);
    49
      }
    50
    
    
    51
    @DELETE
    52
    @Path"id"
    53
    公共无效删除@PathParam"id"字符串ID) |
    54
    存储库.删除ById(ID);
    55
    }

    57
    
    

    管理数据库、代码和集成总是很难的,即使在云上也是如此。事实上,服务器仍然存在,有人需要观察它,运行安装和备份,并维护一般的健康。十二因素 APP需要严格地将配置与代码分离。

    谢天谢地,Platform.sh提供了一个PaaS,用于管理服务,如数据库和消息队列,并支持多种语言,包括 Java。一切都建立在基础设施作为代码 (IaC) 的概念之上,通过YAML文件管理和调配服务。

    以前的帖子中,我们提到如何Platform.sh主要使用三个文件完成:

    1) 一个定义应用程序使用的服务 (服务.yaml).

    Yaml