隔离域逻辑

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(270)   2023-09-06 09:14:57

在一个设计模式课程中,我对建模域逻辑进行了一次有趣的讨论。具体来说,它是关于隔离域逻辑。应用程序通常分为三个部分:

  1. 演示(例如桌面 GUI、浏览器、网络服务)
  2. 领域逻辑
  3. 基础架构(例如持久性存储、电子邮件)

全班同学发现依赖箭头指向领域逻辑部分很有趣。他们问:“图表是故意弄错的吗?域逻辑部分不应该依赖于持久性存储吗?”这是一个很好的问题。我想在这里分享和发布讨论和解释。

经常被误解

大多数开发人员通常会有这种误解。

而这种误解很大程度上是由于操作顺序造成的。它通常以表示层中的触发器(例如用户单击按钮或链接)开始,然后调用域逻辑层中的某些内容,然后调用基础结构层中的内容(例如更新数据库表记录)。

虽然这 是正确的操作顺序,但领域逻辑层的实现方式有些微妙。这与依赖倒置有关。

依赖倒置原则

域逻辑层可能需要基础设施层的一些东西,比如某种形式的访问以从持久性存储中检索。通常的模式是:DAO 和存储库。我不会在这里解释这两种模式。相反,我会指出接口定义放在域逻辑层中,而它们的实现放在另一个单独的层中。

将(DAO 和存储库)接口定义放在领域逻辑层中意味着定义它的是领域逻辑层。它是一个规定需要哪些方法以及期望返回类型的方法。这也标志着领域逻辑的边界。

接口和实现之间的这种分离可能很微妙,但却很关键。仅放置接口定义允许域逻辑部分不受基础架构细节的影响,并允许在没有实际实现的情况下对其进行单元测试。在单元测试期间,接口可以具有模拟实现。这种细微的差异在快速验证(开发团队对)业务规则方面产生了很大的不同。

这种分离是经典的依赖倒置原则在起作用。域逻辑(高层模块)不应依赖于 DAO 和存储库实现(低层模块)。两者都应该依赖于抽象。领域逻辑定义了抽象,而基础设施的实现依赖于这些抽象。

我见过的大多数新手团队将 DAO 和存储库接口与其特定于基础架构的实现放在一起。例如,假设我们有一个 StudentRepository 及其特定于 JPA 的实现 StudentJpaRepository。我通常会发现新手团队将它们放在同一个包中。虽然这很好,因为应用程序仍会成功编译。但是分离消失了,领域逻辑不再孤立。

现在我已经解释了域逻辑部分为什么以及如何不依赖于基础设施部分,我想谈谈表示部分是如何意外地与域逻辑纠缠在一起的。

分离呈现

我经常在新手团队中看到的另一件事是他们最终如何将领域逻辑与他们的表现纠缠在一起。这导致了这种讨厌的循环依赖。这种循环依赖在逻辑上比物理上更重要。这使得检测和预防变得更加困难。

我不会在这里使用丰富的 GUI 演示示例,因为 Martin Fowler 已经写了一篇很棒的文章。相反,我将使用基于网络浏览器的演示文稿作为示例。

大多数基于 web 的系统将使用 web 框架来表示。这些框架通常实现某种形式的 MVC(模型-视图-控制器)。使用的模型通常是直接来自领域逻辑部分的模型。不幸的是,大多数 MVC 框架都需要一些关于模型的东西。在 Java 世界中,大多数 MVC 框架都要求模型遵循 JavaBean 约定。具体来说,它要求模型具有公共零参数构造函数以及 getter 和 setter。零参数构造函数和设置器用于自动将参数(来自 HTTP POST)绑定到模型。吸气剂用于在视图中渲染模型。

由于演示中使用的 MVC 框架的这一隐含要求,开发人员将向其所有域实体添加公共零参数构造函数、getter 和 setter。他们会证明这是必需的。不幸的是,这妨碍了域逻辑的实现。它与演示文稿纠缠在一起。更糟糕的是,我看到域实体被代码污染,这些代码发出 HTML 编码的字符串(例如,带有小于和大于符号编码的 HTML 代码)和 XML,仅仅是因为表示。

如果可以将域实体实现为 JavaBean,那么直接在演示中使用它就可以了。但是如果域逻辑变得有点复杂,并且需要域实体失去它的 JavaBean 特性(例如,不再有公共零参数构造函数,不再有 setter),那么建议域逻辑部分实现域逻辑,并通过创建另一个 JavaBean 对象来满足其 MVC 需求,从而使表示部分适应。

我经常使用的一个示例是用于对用户进行身份验证的 UserAccount。在大多数情况下,当用户希望更改密码时,还需要旧密码。这有助于防止未经授权更改密码。下面的代码清楚地显示了这一点。

public class UserAccount {
  ...
  public void changePassword(
      String oldPassword, String newPassword) {…}
}

但这不遵循 JavaBean 约定。如果 MVC 表示框架不能很好地与 changePassword 方法配合使用,一种天真的方法是删除错误的方法并添加 setPassword 方法(如下所示)。这削弱了域逻辑的隔离性,并导致团队的其他成员到处实现它。

public class UserAccount {
  ...
  public void setPassword(String password) {…}
}

开发人员了解表示取决于领域逻辑很重要。而不是相反。如果表示有需求(例如 JavaBean 约定),那么它不应该让域逻辑符合该需求。相反,表示应该创建具有相应域实体知识的附加类(例如 JavaBeans)。但不幸的是,我仍然看到很多团队仅仅因为展示而强迫他们的领域实体看起来像 JavaBeans,或者更糟的是,让领域实体创建 JavaBeans(例如 DTO)用于展示目的。

安排技巧

这是安排您的应用程序的提示。将您的域实体和存储库放在一个包中。将您的存储库和其他基础设施实现放在一个单独的包中。将与演示相关的类保存在自己的包中。请注意哪个包取决于哪个包。包含领域逻辑的包最好位于这一切的中心。其他一切都取决于它。

使用 Java 时,包看起来像这样:

  • com.acme.myapp.context1.domain.model
    • 将您的域实体、值对象和存储库(仅限接口定义)保存在这里
  • com.acme.myapp.context1.infrastructure.persistence.jpa
    • 将基于 JPA 的存储库和其他与 JPA 持久性相关的实现放在此处
  • com.acme.myapp.context1.infrastructure.persistence.jdbc
    • 将基于 JDBC 的存储库和其他与 JDBC 持久性相关的实现放在此处
  • com.acme.myapp.context1.presentation.web
    • 将您的网络/MVC 演示组件放在这里。如果表示所需的域实体不符合 MVC 框架要求,请在此处创建其他类。这些额外的类将适应域实体以用于表示目的,并且仍然保持域实体与表示分离。

请注意,我使用了 context1,因为在给定的应用程序(或系统)中可能有多个上下文(或子系统)。我将在以后的帖子中讨论具有多个上下文和多个模型。

目前为止就这样了。我希望这个简短的解释可以让那些想知道为什么他们的代码以某种方式排列和拆分的人有所了解。

感谢 Juno Aliento 在这次有趣的讨论中帮助我上课。

节日快乐!

标签2: Java教程
地址:https://www.cundage.com/article/jcg-isolating-domain-logic.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...