在洋葱、六边形或简洁架构中,域模型是否可以包含与数据库中的域模型不同的属性?

use*_*022 5 software-design n-tier-architecture hexagonal-architecture onion-architecture clean-architecture

我问你谁知道并有使用任何分层架构(洋葱、六边形、干净等)构建软件的经验。每当我在谷歌上搜索软件架构时,人们都会有不同的观点,并以不同的方式解释相同的架构。

条款

在您阅读问题之前,有些术语可能会让您感到困惑,因此我将在下面对其进行定义。我不确定我是否对它们有“正确”的定义,但我从互联网上收集了这些信息。如果我有误解,请告诉我。

领域层:包含企业/业务逻辑并使用领域模型。位于中心并且不依赖于除领域模型之外的任何其他层。

应用层:包含应用逻辑,从基础设施层接受DTO,传递View Model

DTO(Data Transfer Object):用于在层与层之间传输数据的类、JSON字符串等。可能是一个纯数据容器。

VM(View Model):从应用层传递到表示层的DTO。

DO(Domain Model):域层使用的类、JSON字符串等。可能是一个纯数据容器。

VO(Value Object):数据库实体(一个数据库行),或者数据库使用的一种数据格式。可以从数据库层转移到应用层。

概括

在洋葱、六边形或简洁架构中,域层位于中心(即域层不依赖于域模型以外的任何层,域模型用于将数据传输到其他层或接受来自更高层的数据)。

这意味着域使用的域模型(DTO、POJO、VO 或其他)可能与数据库用于保存持久数据的模型不同。

我画了一个图表,以便我可以给你更好的解释。

在此处输入图片说明

在此处输入图片说明

第一季度

请看第二张图片的红色部分。

与传统的分层或 n 层架构不同,如果领域层位于中心,那么领域模型是否可以拥有比数据库实体(行)更多的属性(或不同的属性)?

例如,假设域层使用一个名为Person的类。用户请求所有在服务器中注册的人的照片。让我们假设数据库只包含所有人的姓名。但是,我们可能会使用其他网络服务器通过姓名请求某人的照片。所以应用层会从数据库中读取所有的名称,并通过这些名称,通过 HTTP 请求从其他 Web 服务器获取所有图片。之后,带有姓名和图片的Person列表将作为视图模型(DTO)发送给用户。

问题二

持久层可能由数据库、文件系统、其他 Web API 等组成。

表示层可以是网站、桌面应用、移动应用、Web API 等。

这两层都是基础设施层的一部分,都依赖于应用层,而应用层只依赖于领域层。

当应用层接受来自表现层的请求时,没有问题,因为表现层调用应用层,表现层知道应用层。

大多数时候,应用层需要从持久层获取数据。

应用层无法在没有任何依赖的情况下调用持久层,因为它不知道持久层中的任何类。

这就是我目前的理解,谁能给我一个清晰的解释,数据应该如何流动以及从低层到高层的通信是如何完成的?

对于那些想写代码的人,我更喜欢 C#。

Ren*_*ink 3

Q1:> 领域模型可以比数据库实体(行)拥有更多的属性(或不同的属性)吗?

是的,可以,因为领域模型不是数据库模型。你不应该混合它们,因为它们会因不同的原因而改变。域模型(在干净的架构中是实体)会因应用程序独立业务规则的更改而发生更改。由于数据持久化方式的改变,数据库模型发生了变化。如果将它们混合在一起,就会违反单一责任。

应用程序层不可能在没有任何依赖的情况下调用持久层,因为它不知道持久层中的任何类。

这就是我到目前为止的理解,有人可以给我一个清楚的解释数据应该如何流动以及如何从下层到高层进行通信?

有一种方法。这称为依赖倒置。如果您正在进行结构化编程,您的代码将如下所示:

+-----+   f()    +-----+
|  A  |  ----->  |  B  |
+-----+          +-----+
Run Code Online (Sandbox Code Playgroud)

有一个类A调用fclass 上的方法B

如果您使用 C#,您将看到using Bin class A。如果您使用 java 它将是一个import B. 无论您使用什么编程语言。类的名称B将出现在 中A

但如果它是usingorimport语句,则意味着编译器知道。因此你有一个编译时依赖 A-> B

当代码执行时,控制流运行时依赖)也是A-> B

让我们看看另一种方法

+-----+   f()    +------------+       +-------+
|  A  |  ----->  | BInterface | <---- | BImpl |
+-----+          +------------+       +-------+
Run Code Online (Sandbox Code Playgroud)

在这种情况下,A取决于B我在此处调用的前者的抽象BInterface,并且实现已移至BImpl实现该接口的类。

运行时,控制流仍然从Aintof的方法进行BImpl,但在编译时 ABImpl依赖于,因此从到 的BInterface依赖关系指向控制流BImplBInterface

您可以使用多态性来实现这一点。这种方法称为依赖反转,因为您反转了依赖关系,使其指向控制流。

回到你的问题

您的应用程序层应该定义一个可用于收集实体的接口。该接口通常称为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);
} 
Run Code Online (Sandbox Code Playgroud)

更好的方法是

public interface PersonRepository {

    public List<Person> findByCriteria(PersonCriteria criteria);
} 

public class PersonCriteria {
    
      private String firstName;
      private MatchMode firstNameMatchMode; // something like STARTS_WITH, ENDS_WITH, CONTAINS

      // setters omitted
}
Run Code Online (Sandbox Code Playgroud)

您可能想要实现更复杂的标准,但请始终记住,永远不应该公开实现细节。