问题描述
我问的是谁知道的并且有使用任何分层体系结构(洋葱,六角形,干净的等)构建软件的经验。每当我在Google上搜索软件体系结构时,人们都会有不同的看法并以不同的方式解释相同的体系结构。
条款
在阅读问题之前,某些术语可能会使您感到困惑,因此在下面定义它们。我不确定是否对它们有“正确的”定义,但是我从互联网上收集了这些信息。让我知道我是否误会了。
域层:包含企业/业务逻辑并使用域模型。位于中心,除域模型外,不依赖其他任何层。
应用程序层:包含应用程序逻辑,接受来自基础结构层的DTO,并传输视图模型
DTO(数据传输对象):用于在层之间进出数据的类,JSON字符串等。可能是纯数据容器。
VM(视图模型):从应用程序层传递到表示层的DTO。
DO(域模型):在域层中使用的类,JSON字符串等。可能是纯数据容器。
VO(值对象):数据库实体(数据库行)或数据库使用的数据格式。可以从数据库层转移到应用程序层。
在洋葱,六角形或干净的体系结构中,域层位于中心(即,域层不依赖于域模型以外的任何层,该域模型用于将数据传输到其他层或从更高层接受数据层)。
这意味着该域使用的域模型(DTO,POJO,VO或任何其他模型)可能与数据库用于保存持久性数据的模型不同。
我画了一个图,以便给您更好的解释。
第一季度:
请查看第二张图片的红色部分。
如果域层位于中心层而不是传统的分层或n层体系结构,那么域模型是否可以具有比数据库实体(行)更多的属性(或不同的属性)?
例如,假设域层使用名为 Person 的类。用户请求在服务器中注册的所有人的图片。让我们假设数据库仅包含所有人的姓名。但是,我们可能会使用其他Web服务器通过名称请求提供人的照片。因此,应用程序层将从数据库中读取所有名称,并使用这些名称通过HTTP请求从其他Web服务器获取所有图片。之后,带有名称和图片的人物列表将作为视图模型(DTO)发送给用户。
第二季度:
表示层可以是网站,桌面应用,移动应用,Web API等。
这两个层都是基础结构层的一部分,并且取决于应用程序层,但是应用程序层仅取决于域层。
当应用程序层接受来自表示层的请求时,就没有问题,因为表示层调用了应用程序层,而表示层知道了应用程序层。
在大多数情况下,应用程序层需要从持久性层获取数据。
应用程序层无法在没有任何依赖性的情况下调用持久层,因为它不知道持久层中的任何类。
到目前为止,这就是我的理解方式,有人可以给我清楚的解释数据应该如何流动以及如何从较低层到较高层进行通信吗?
对于那些想写代码的人,我更喜欢C#。
解决方法
Q1:>域模型是否可以具有比数据库实体(行)更多的属性(或不同的属性)?
可以,因为域模型不是数据库模型。您不应该混合使用它们,因为它们会因不同的原因而变化。域模型(在干净的体系结构中的实体)由于与应用程序无关的业务规则的更改而发生了变化。数据库模型的更改是由于更改了数据保留方式。如果将它们混合使用,则会违反单一责任。
应用程序层无法在没有任何依赖性的情况下调用持久层,因为它不知道持久层中的任何类。
到目前为止,这就是我的理解方式,有人可以给我清楚的解释数据应该如何流动以及如何从较低层到较高层进行通信吗?
有一种方法。这称为依赖反转。如果您要进行结构化编程,则代码将如下所示:
+-----+ f() +-----+
| A | -----> | B |
+-----+ +-----+
有一个类A
调用类f
上的方法B
。
如果您使用的是C#,您将在类using B
中看到一个A
。如果您使用的是Java,它将是import B
。无论您使用哪种编程语言。类B
的名称将显示在A
中。
但是,如果它是using
或import
语句,则表示编译器知道。因此,您具有编译时间依赖性 A
-> B
。
执行代码时,控制流(运行时依赖项)也是A
-> B
。
让我们看看另一种方法
+-----+ f() +------------+ +-------+
| A | -----> | BInterface | <---- | BImpl |
+-----+ +------------+ +-------+
在这种情况下,A
依赖于我在这里称为B
的前一个BInterface
的抽象,并将实现移至类BImpl
来实现该接口。 / p>
在运行时,控制流仍从{{1}}进入A
的方法f
,但在编译时间 BImpl
和A
取决于BImpl
,因此,从BInterface
到BImpl
的依赖点与控制流。
您可以使用多态来实现。这种方法称为依赖倒置,因为您将依赖倒置,使其指向控制流。
返回您的问题
您的应用程序层应定义一个可用于收集实体的接口。此接口通常称为BInterface
。然后,您的数据库层可以实现该Repository
(依赖关系倒置)。
在干净的架构中,它看起来像这样
请记住用例和数据库实现之间的双线。这些线称为架构边界。越过这条线的每个依赖项都必须指向同一方向,以遵循干净的体系结构依赖项规则。
还要确保不要犯将特定于实现的东西放在接口中的错误。
接口是一种抽象,因此告诉人们可以做什么,而不是如何完成。
Repository
一种更好的方法是
public interface PersonRepository {
// WRONG - because the where is usually a part of an SQL or JPQL
// and thus exposes the implementation.
public List<Person> findByCriteria(String where);
}
您可能希望实施更复杂的条件,但要始终保持绝对不要公开实施细节。
,Q1:领域模型是否可以具有更多属性(或不同 属性)而不是数据库实体(行)?
当然。两种模型可能具有不同的属性。 “持久性端口”(“存储库”)实现,即适配器会将一个模型相互转换。
第二季度:
在大多数情况下,应用程序层需要从 持久层。
应用程序层无法调用持久性 层没有任何依赖关系,因为它不知道任何依赖关系 持久层中的类。
为了从持久层获取数据,应用程序层调用“存储库”(DDD语言),即“持久数据端口”(十六进制体系结构语言)。该存储库(端口)属于域,因此应用程序层称为域层。
数据库适配器实现端口。适配器属于基础结构层,这是可以的,因为基础层不仅取决于应用程序层,还取决于域。
如果您有兴趣,这是我有关六角形建筑的文章: