微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

干净的架构边界

如何解决干净的架构边界

我对简洁架构比较陌生,我有一些关于将CA用于后端 spring 项目的问题。

  1. 应用程序是否必须(对 CA 来说是一种好的做法)模块化,还是可以是一个文件夹中拆分的模块?

  2. 关于业务逻辑,是否应该只针对用例

    我可以在任何层有“验证器”吗?
      例如
        关于大小、可为空、可选字段的验证可能是在 InputRequest 对象上吗?或者例如在 Controller 本身中调用 PaginationValidator ?
        我有以下业务规则时,在存储库中检查是否应该更新字段:“如果字段为空,则不更新,否则更新”。
        如果我有一个用例调用网关,网关是否也可以调用验证器?或者验证必须发生在用例中?
  1. 存储库

      存储库是网关吗?如果是这样,它适合哪一层?
      假设我在 DB、Department 和 Employee 上有两个实体。业务规则是:“要创建一个部门,我也必须创建它的员工,而且只能通过部门”。
        在这种情况下我该怎么办?
        我应该创建两个存储库,因为我只能在创建部门时创建员工,所以我应该通过部门存储库调用员工存储库中的 create() 吗?
        即使我在创建部门时只能创建一个员工,我是否应该有两个用例(一个用于创建部门,一个用于创建员工)?
        如果我有两个用例,我应该通过部门用例调用员工用例还是创建另一个用例来编排它们?
        我是否应该只创建一个用例并创建一个网关来编排存储库?
        还有其他选择吗?
      还有关于存储库,我可以通过存储库调用用例吗?这让我很困惑,因为理论上,Usecase 和存储库会有接口,并且它的实现可以在同一层,但是箭头会向前和向后移动不是吗?
  2. DTO

      是否有第二个构造函数需要“输入”,并且这个构造函数填充相应的字段?例如部门有一个期待aDepartmentRequest的构造函数
      如果我必须创建三个“层”域,是否有任何规则?比如 DepartmentRequest、Department 和 DepartmentEntity?如果不是,我可以为一个实体只有两个或一个对象吗?
      所有转换都应该通过汇编器/转换器吗?


我已经在互联网上搜索过,但所有这些信息似乎都太抽象了,有时还模棱两可,如果有人能用例子给我解释一下,我将不胜感激! 谢谢!!

解决方法

应用程序必须模块化还是可以是一个模块拆分成文件夹?

我可以模块化,但不能。模块化在 Java 或 C++ 等编译语言中具有优势,因为您不能在模块之间引入循环。例如。如果在 Java 中 JAR B 依赖于 JAR A,则不能将依赖从 JAR A 引入 JAR B,因为这会引入一个循环,然后在 JAR B 构建之前无法构建 JAR A,反之亦然。

拆分为文件夹、包或命名空间(取决于您的语言如何命名)可以根据您的需要进行。看看我对问题 Who must create services with package modifier if I implement architecture from “Clean Architecture” book 的回答。

关于业务逻辑,应该只在用例上吗?

所有业务规则都应该放在用例层或下面,因为干净架构的目标是让这部分易于测试。应用程序特定的业务逻辑应放在用例中,应用程序不可知规则应放在实体中。

关于输入,它可以在控制器上还是在域(请求)中?

将特定于控制器的验证与业务验证分开。控制器特定应置于控制器级别,业务特定应置于用例和/或实体层。

例如如果您有一个使用 JSON 的休息控制器,并且此 JSON 包含一个日期字符串。然后控制器负责将字符串解析为日期对象,例如作为 ISO 日期,或者它可能会尝试解析不同的格式。当您将解析的日期对象传递给用例时,用例有责任验证此日期在用例的上下文中是否有效。例如。如果您有用例“ScheduleMeeting”,则日期应该在未来。

用例层应该抛出像 ScheduledTimeElapsedException 这样的业务异常。控制器层可以捕获此异常并决定要做什么,例如休息服务可能会返回 HTTP 状态代码和/或带有更详细描述的 JSON,也可能是国际化的描述,具体取决于请求客户端的语言设置。

存储库

存储库是网关吗?如果是,它适合哪一层?

存储库是用例需要从中获取实体并将实体放入其中的接口。

网关是一种实现,它知道如何联系外部服务或数据库以从中获取数据并将数据存储到其中。

enter image description here

假设我在 DB 上有两个实体,Department 和 Employee。业务规则是:“要创建一个部门,我也必须创建它的员工,而且只能通过部门”。 这种情况我该怎么办?

是否应该创建两个存储库,因为我只能在创建部门时创建员工,所以我应该通过部门存储库调用员工存储库中的 create() 吗?

您应该应用接口隔离原则并创建特定于用例的存储库接口。这个接口应该只定义用例需要的方法。由于业务规则是创建两者,因此该界面可能如下所示。

public void MyUseCaseRepository {
    public void persistDepartment(Department dep,List<Employee> employees);
}

如果你应用接口隔离原则,下面的其他问题也应该得到回答。

我是否应该只创建一个用例并创建一个网关来编排存储库?

然后实现可以负责存储两者或不存储。

您现在可能会想“但后来我将业务逻辑移到了网关中。”。阅读我对问题 Should Repositories Throw Domain Errors 的回答。

还有关于存储库,我可以通过存储库调用用例吗?这让我很困惑,因为理论上Usecase和repository都会有接口,而且它的实现可以在同一层,所以有什么规定吗?

简洁的架构并没有真正对此做出声明。存储库实现位于接口适配器层,就像控制器一样。因此理论上他们可以访问用例。但我认为这不是作者的意图。我认为 Bob 大叔认为它更像是六边形架构,存储库是适配器,而控制器是端口。我不会从存储库实现中调用用例。最后这个问题只能由作者来回答了。

The clean architecture

DTO

它是否有第二个构造函数需要一个“输入”,并且这个构造函数填充相应的字段?

我猜您的意思是当您说“输入”时用例返回的 ResponseModel

从架构的角度来看,这是可以的,因为 DTO 属于控制器或接口适配器层,因此依赖方向是正确的。

但是当 DTO 的构造函数读取响应模型并填充它的字段时,它也会将响应模型的值转换为它们的 DTO 表示。也许您想将这种转换与 DTO 本身分开,并将其放在可以单独测试的演示器实现中。

如果我必须创建三个“层”域,是否有任何规则?比如 DepartmentRequest、Department 和 DepartmentEntity?如果不是,我可以为一个实体只有两个或一个对象吗?

您可以在干净的架构中看到规则。依赖箭头从外层开始,在下一个内层结束。没有箭头指向例如从控制器层到实体层。因此,如果您对架构非常严格,控制器不应访问实体。

如果你把所有东西都放在一个对象里,例如Department 并且您几乎将其用于所有内容,例如作为 DTO,作为数据库实体和域实体,那么您将所有这些不同的职责结合起来。

因此,如果您修改数据存储在数据中的方式,您可以轻松破坏 DTO 表示,反之亦然。如果要修改业务规则,也可以轻松破坏数据库表示,反之亦然。最后,如果您修改业务规则,您还可以打破 DTO 表示。

这些对象看起来很相似,但它们的职责不同,因为它们服务于不同的客户端。因此,如果您将所有内容都放在一个对象中,您将违反单一职责规则。

我不能告诉你一次违反责任的所有影响,因为这远远超出了这个问题。

,
  1. 想想 Bob,如果软件环境/Lang 没有模块概念,他会不会使用这种模式?

架构就是架构,文件夹和模块不是架构,它们是权衡。模块可以帮助设计它,这样业务层就不能访问 jdbc 之类的东西,代价是更多的模块定义。

  1. 用例是应用程序的公共 api,因此验证在那里进行。用例应仅实现桥接模式中的桥接,桥接的另一端是例如一个存储库。

桥梁(例如接口和边界类)充满了需求。用例要求很高,可能需要 5 或 10 个不同版本的相同操作。它可以表示为 en Enum 或存储库中的 10 个方法,这些决定是权衡/风格/等。

控制器将验证请求是否有效(将它们视为超市中的钞票检查器)。但业务逻辑(POS)将验证优惠券是否有效以及价格折扣是多少。

硬币分配器的存储库可以决定它是否应该分配许多小硬币以及可能以哪种货币分配。硬币分配器可以验证机器中是否有足够的硬币。

不同的层有不同的验证。在类型化语言中,许多验证都是自动化的,例如你声明了一个 UUID,如果它实际上不是,那么它会在运行时出错。

  1. 您正在混合其他模式的抽象。存储库实现了来自用例的桥梁。来自用例的需求。在业务应用中,存储库中的公共 api 是它强制实现的所有桥接的总和。代码库是否选择在 API 层中实现所有桥接是一种权衡。

通常,Department 和 User 是直接从您对世界的心智模型映射出来的,但这并不是您设计软件时的最佳方式。

用例可能应该在 NewDepartment 和 NewDepartment 存储库等上运行。用户和部门甚至可能不存在于软件中。相反,存在 20 种不同的属性组合,它们会一起变化。你可以用复仇者或有意义的东西来称呼他们。应该尊重无处不在的语言,但是当它不适用时,“Thanos”可能是一个模块或概念的好名字。每个人都知道 Thanos 在您的项目中做了什么,它是清除数据或 w/e 的模块。或者,您可能将“欢迎”作为新部门等的概念。同样,尝试与无处不在的语言相协调,并尝试将自己从不会一起改变的事物的思维模式中解放出来。例如。客户状态和客户名称通常在完全不同的时间更改。

每个用例通常都有一个定义明确的桥,因此有一个专门的存储库。

  1. 权衡。不要将您的公共 API 耦合到数据库,例如不要返回数据库元组。将元组转换为对象并不会改变这一事实,因此通常至少需要一次转换。当业务逻辑中有很多代码时,通常还会进行二次转换,以允许数据库更改。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?