前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >整洁架构、DDD 和 CQRS 简介

整洁架构、DDD 和 CQRS 简介

作者头像
IT大咖说
发布2022-03-04 13:11:35
2.8K0
发布2022-03-04 13:11:35
举报
文章被收录于专栏:IT大咖说IT大咖说

介绍

在这篇博文中,我将介绍整洁架构(Clean Architecture ),它是一种现代、可扩展的正式软件架构,适用于现代 Web 应用程序。接下来,我将讨论DDD(领域驱动设计)如何适应这幅图景,以及 DDD 概念如何与清洁架构完美契合,从而产生一种称为清洁 DDD 的方法。最后,我介绍了命令查询职责分离 (CQRS),并描述了它如何补充和增强 Clean DDD 解决方案,以创建优雅、健壮、可扩展和可测试的软件系统。

清洁架构

清洁建筑是一种相对“现代”的正式建筑,因为它不到十年的历史。它随着时间的推移从其他几种架构演变而来,包括六边形架构、端口和适配器以及洋葱架构。 在这篇文章中,Bob 大叔强调了所有前身架构和清洁架构都具备的五个品质:

  1. 框架独立性:架构与第三方框架解耦。
  2. 可测试:该架构易于编写单元测试。
  3. UI 独立性:架构可以从用户界面中拔出
  4. 数据库独立性:架构与底层数据存储分离。
  5. 外部代理独立性:架构的业务规则是孤立的,对外界一无所知。

Clean Architecture 可以被可视化为一系列同心圆,每个圆代表应用程序的不同层。使架构结合在一起的原则称为Dependency Rule

“使这个架构工作的最重要的规则是依赖规则。这条规则说源代码依赖只能指向内部。内圈中的任何东西都无法知道外圈中的某物。特别是某物的名称内圈中的代码不得提及外圈中声明的内容。这包括函数、类、变量或任何其他命名的软件实体。

这是一个很好的起点,但我想进一步详细说明。清洁架构应用程序中的层:

  • 可能永远不知道围绕(在)它的层。
  • 也可能永远不知道与其相邻的层。
  • 在实时应用程序中通过依赖倒置在功能上相互连接——即通过在外层实现的抽象(接口)。

整洁架构的显着特征是组成它的同心层围绕着一个包含抽象和业务逻辑的中央核心。这些抽象的实现,连同它们的外部依赖,被推到外层。在描述这种布局时,我喜欢使用“外围”的术语来指代外部实现层,而“核心”来指代内部层。请注意,这是我的命名约定,此时您不太可能在其他任何地方找到它。

同样,我想强调一下我们如何明确地使用依赖倒置原则来确保内部层(纯逻辑和抽象)永远不会有任何外部层(实现)的知识。内部层使用这些层中定义的抽象,而实际的实现逻辑存在于外部层中。重申一下:依赖倒置原则指出细节依赖于抽象;抽象不依赖于细节。因此,我们正在区分什么是本质(核心)和什么是细节(外围)。使用依赖注入,通常通过控制容器的反转,所有内容都在完成的解决方案中结合在一起。

整洁的领域驱动设计

我对 Clean DDD 的解读

整洁的领域驱动设计代表了软件架构开发的下一个合乎逻辑的步骤。这种方法源自Blob的原始架构,但在概念上略有不同。相同之处在于它在较高级别使用相同的同心层方法,但是使用域驱动设计来构建内核。此外,DDD 推动将域分离为不同的有界上下文也为这种设计提供了信息,因为这些有界上下文现在成为堆栈每一层水平分离的指南。这是一个真正的、现代的、以领域为中心的模型,用于构建和交付复杂的业务应用程序。

核心层

领域层

域层是核心中最中心的层。该层是使用 DDD 原则构建的,其中没有任何内容对它之外的任何内容有任何了解。在大多数情况下,这里不使用依赖注入,尽管事件调度程序实现可能会出现罕见的异常。领域层中的领域服务和其他业务逻辑甚至不需要真正位于接口后面,因为该逻辑不太可能随着时间而改变,并且不需要多态性。在使用接口确实有意义的领域领域,例如使用策略模式来封装不同的业务逻辑,继续使用它们;否则,只需将域服务直接注入需要它们的类中。

应用层

应用层非常重要,因为它基本上是将领域层与外层绑定的“粘合剂”。它几乎就像一个中间层。应用层声明了代表基础设施、持久性和表示组件的接口和其他抽象。这些组件的实际实现不在这一层中声明,而是通过依赖注入提供给应用程序组件。

该层还负责编排:它实现了操作域对象和启动域工作流的高级逻辑。这样,它本身不包含任何一流的业务逻辑,而是通过对领域层的调用来组织该逻辑。它可以协调任务并将工作委托给域,但它不包含业务规则或维护业务状态。

应用层同样使用注入的持久化接口执行持久化操作。这就是存储库模式或 CQRS 发挥作用的地方(解释如下)。由于不同的编排操作,它将数据传输对象(DTO) 传递到表示层。同样,它还使用注入的基础设施接口与操作系统和其他外部资源进行通信。

外围层

持久层

持久层包含 应用层中声明的持久性接口的实现。它还包含专门的持久性模型(数据访问)类,这些类可能是也可能不是数据库表的镜像(特别是如果您使用对象关系映射器,又名 ORM),或者可能代表数据库查询的投影。这是对数据库进行实际读/写的所有硬逻辑所在的位置。

基础设施层

基础设施层包含应用程序层中声明的基础设施接口的实现。这里没有太多要说的,因为它是你所期望的:它封装了与操作系统、外部 API 等通信的逻辑。与外部消息队列通信的实现细节以及通信的服务都在这里与任何其他外部机构。

表示层

表示层是一个 API 层,它汇集了所有应用程序层组件,并将它们注入适当的实现(通常使用 IOC 容器)。在我的解释中,这一层不是用户界面(UI),而是呈现 UI 与之通信的外观。在 Web 应用程序中,表示层是一个 MVC 应用程序,它使用 Web 协议(如 REST、GraphQL 或 Web 套接字)与 UI 通信。展望未来,当我谈到 MVC 控制器时,要知道我总是将它们称为表示层组件。

现在,您需要注意一些事情。我研究过的一些资料将 Web API 视为系统的应用层。换句话说,应用层和表示层似乎是一回事。我强烈不同意这一点。应用程序层是它自己的动物,如果需要,您应该始终能够将其与表示逻辑分离。

用户界面

用户界面是该架构中绝对最高的概念层。这是用户直接与之交互的代码。一些示例可能是 Angular 或 React 等,它们在用户的 Web 浏览器中运行,或者使用 Windows Presentation Foundation (WPF) 构建的桌面应用程序。一些消息来源将其与表示层混为一谈,但我认为将其分开很重要,至少在 Web 应用程序中是这样。如果架构良好,您的系统应该能够毫不费力地移除 UI 并用不同的 UI 替换。

公共层

公共层是一个库或一组库,用于横切关注点,例如日志记录、文本操作、日期/时间算法、配置等,它们对整个系统都是全局的. 公共层中的组件和接口可以在堆栈的任何层中使用(UI 除外,它可能完全断开连接,在 Web 应用程序的情况下,完全在用户的浏览器中运行)。公共层包含组件和功能的实现细节,这些细节足够通用,可以在应用程序的任何地方使用。在这一点上,这里绝对不应该有任何业务逻辑或与域有关的任何事情。

努力防止这一层膨胀失控。当怀疑某个东西是否属于公共层时,想想自己,这个组件是否可以在完全不同的软件系统中重用,甚至可以放入可重用工具包中?如果答案是“否”,那么您真的需要考虑它是否是一个横切关注点,或者它是否属于系统的另一部分。

CQRS 入门

CQRS 代表 Command/Query Responsibility Segregation,这是一件很棒的事情。这是一种架构设计模式,它允许更高级别的层(例如表示层)与其他层(例如应用层)进行通信 - 例如,表示层内的控制器将调用由应用程序执行的命令和查询层组件。CQRS 与 Clean Domain-Driven Design 完美契合,因为它是一种行为模式:Clean DDD 是什么,CQRS 是如何。

在我深入挖掘之前,我想明确一点,你不需要使用 CQRS 来实现 Clean Architecture 或 Clean DDD 解决方案,但你为什么不使用它呢?为了争论,另一种方法可能是将您的编排逻辑封装在应用程序层服务中,这些服务直接注入您的控制器中。一切都很整洁,并尊重依赖倒置原则。但是,您已经丧失了 CQRS 提供的好处,因为它抽象了跨层边界的组件之间的通信过程本身。CQS 原则指出:

  • 查询系统数据的高级操作不应该产生任何副作用——即修改状态。这些称为查询。查询通过 DTO 将数据返回到表示层。
  • 修改系统的高级操作不应返回数据。这些应该会产生副作用,修改系统的状态,然后完成。这些被称为命令。
  • 在实践中,命令可能会返回一小部分元数据,例如新创建的实体的 ID,但仅此而已。命令也可能返回 ack/nack 响应。
  • 命令执行的另一个结果可能是错误条件,在这种情况下,命令应该抛出异常。

CQS 自身

CQS 可以直接在 Clean DDD 解决方案中实施。为此,您通常会将控制器方法分解为命令/查询(即写入/读取)操作,并且永远不会违反两者之间的分隔。目的是创建一个基于任务的界面,它处理行为,而不仅仅是保存数据或执行其他 CRUD 操作。请注意:这是 CQS 和 CQRS 与 DDD 相交的地方——操作本身通常会使用您正在使用的有界上下文的普遍语言以业务流程命名. 请注意,这种简单、优雅的设计也促进了单一职责原则,因为每个操作都更具凝聚力,并且更好地对应 UI 操作。这促进了接近用户体验 (UX) 驱动开发的开发过程。

CQS 的反面是我经常看到的反模式:命令和查询之间的零分离。在这种非设计中,没有真正的方法来理解给定操作的副作用是什么,因为总是有一些 Rube Goldberg 逻辑在后台运行。这在已有 10 多年历史的遗留应用程序中很常见,但许多应该更了解的职业开发人员仍然以这种方式构建解决方案。另一种常见的反模式是在控制器(Web API)上公开 CRUD 操作,然后业务逻辑分散在整个应用程序中,例如在 UI 本身中或更糟的是在存储过程中的数据库中。这会产生迷宫般的、脆弱的解决方案,在极其荒谬的程度上违反了开放/封闭原则

CQRS 是 CQS+

命令查询职责分离,是什么?CQRS 接受命令和查询并将它们转换为一流的对象。使用 CQS、基于任务的接口的解决方案可以很容易地重构为 CQRS,因为逻辑分离已经存在。两种模式的最大区别在于 CQS 中的命令/查询是方法;在 CQRS 中,模型. 这里的区别很重要。通过将操作视为模型,您将在应用程序中引入令人难以置信的可扩展性。这与 .NET 团队在过去引入任务并行库 (TPL) 时所做的没有什么不同:他们采用了在应用程序中开发异步控制流的繁重过程,并将所有这些抽象为一流的对象,可以独立于依赖它们的代码进行处理。

以下是使用 CQRS 的几个明显好处:

  • 可扩展性:对系统的读操作通常比写操作多得多。在 CQRS 下,查询可以分解成自己的堆栈并独立于命令进行缩放。
  • 性能:您可以构建在紧密耦合模型中不可能实现的优化。
  • 简单:一开始,您通过在您的架构中使用它来支付少量的复杂性,但随着解决方案的增长以满足业务需求,您可以在路上将其收回。随着时间的推移,它更易于维护并且能够更好地管理复杂性。

怎么运行的

在较高级别上,命令/查询在表示层(在控制器操作内部)中实例化并与应用层通信,然后应用层执行业务编排逻辑并执行您感兴趣的高级任务。

一种方法是将命令/查询参数和处理它们的逻辑都放在同一个对象中。该对象使用依赖注入注入每个控制器或使用某种工厂创建。在命令/查询对象上调用Execute()方法并检索结果。我不喜欢这个。

实现 CQRS 的更好方法是将命令/查询与其处理程序分开,并利用进程内消息传递服务将命令/查询对象分派给它们各自的处理程序。这样做有很多好处,但有两个明显的好处是:

  • 它简化了您的代码,因为您不必编写样板来将命令/查询连接到它们各自的处理程序。
  • 您已经在应用程序中创建了一个高级任务执行管道,您可以在其中注入横切关注点,例如错误处理、缓存、日志记录、验证、重试等。这是巨大的。

命令

CQRS 命令总是以现在命令式命名——例如RegisterEmployeeCommand。命令会改变系统状态并返回简单的 ack/nack 或元数据响应,或者它们会抛出异常。它们与域事件的不同之处在于可以拒绝命令;事件不能。命令通常通过应用层与域层交互。这很重要,因为域层包含所有业务逻辑并负责使系统保持一致状态。构成命令的属性应尽可能接近第 3 范式 [Greg Young, CQRS Documents ]。如果你对 1NF/2NF/3NF 之间的区别感兴趣,这篇 Quora 帖子很好地解释了它. 最后,命令通常需要是幂等的。

查询

CQRS 查询也以现在时命名,通常以“Get”开头——例如 GetEmployeeListQuery。查询不会改变系统状态,它们只是返回数据,通常以数据传输对象的形式。返回的 DTO 上的属性的结构接近于第一范式,因为数据可能会从非规范化的数据库查询中返回,并且返回的 DTO 的结构通常会匹配用户的屏幕或某些可由用户使用的规范模型任何客户。

在他的原始规范中,我研究过的大多数专家都同意这一点,Greg Young 指出大多数时候查询应该绕过域层。让我们进一步解开它。为什么我们要直接从应用层传递到表示层?首先,数据模型比用户输入更可靠,并且假设始终是一致的,因此不需要进行验证的数据库。其次,查询不会改变状态,因此进行这种操作的业务域逻辑没有用处。第三,在系统中读取(查询)将比写入(命令)更频繁地发生,因此它们需要快速高效。

命令和查询的其他注意事项:

  • 如 CQS 部分所述,命令和查询都应使用通用语言命名,并表示基于任务的操作,而不是 CRUD。
  • 后缀“命令”和“查询”是可选的,因此请自行决定。只需确保您的命名约定直观且一致。
  • “可选”属性是一种设计味道,可能表明您的基于任务的操作不够内聚。
  • 不要从其他命令/查询调用命令/查询。如果你这样做,这是一个很大的气味。如果您需要在命令逻辑中从数据库中检索数据,那么您应该简单地使用 ORM 或其他方法直接查询数据库。请注意,这是一个典型的例子,其中必须违反某些原则才能维护其他原则。在这种情况下,我们违反了 DRY,以避免危险地将堆栈的两侧耦合在一起并将整个事物变成无法维护的垃圾堆。如果您发现自己在松散耦合和 DRY 之间争论不休,那么松散耦合会胜出。

整洁 DDD + CQRS

一切都导致了这一点。展望未来,我将使用它作为 Web 应用程序开发的主要架构方法,这也是我在演示应用程序中使用的方法。重申一下,高级架构基于清洁架构原则,在系统的同心层之间具有明确的概念分离。

  • 系统的最内层,核心的中心,是Domain层,它是使用DDD原则构建的。应用层围绕着领域层,是核心的一部分。应用层包含命令/查询内部的业务编排逻辑、由外围层实现的接口以及用于与外部层通信的模型类。
  • 表示层、技术实现层(基础设施层)和 持久层位于外围,彼此之间没有明确的了解。表示层本质上是一个 Web API,一些任意 UI,如 Angular,可以与之通信。
  • CQRS 是允许各层在堆栈中优雅地进行通信的基本要素。这就是将一切联系在一起的原因。依赖注入同样对于将组件连接在一起至关重要,同时仍然遵守依赖倒置原则。IOC 容器对此有所帮助。

再一次,没有灵丹妙药,那么有哪些利弊呢?

优点

  • 如果我们构建好我们的抽象,那么这个架构就独立于外部框架、用户界面、数据库等。换句话说,它是灵活的。框架和外部资源可以更轻松地插入/拔出。
  • 该解决方案更具可测试性。
  • 它更具可扩展性。
  • 更容易管理必要的复杂性,即由于试图解决实际业务问题而引入的复杂性。
  • 该解决方案可以由不同的团队进行工作和维护,而不会互相影响。Clean DDD 非常适合敏捷流程。
  • 添加新功能(包括复杂功能)要简单得多,从而使开发人员能够更快地进行调整并更快地发布。随着系统随着时间的推移而增长,添加新功能的难度保持不变且相对较小。这与我工作过的许多遗留系统形成鲜明对比,在这些遗留系统中,随着时间的推移,添加新功能变得更加昂贵和困难,直到最终整个事情越过了可维护性的悬崖并且必须报废。
  • 如果解决方案沿有界上下文线正确分解,则将其部分转换为微服务变得很容易。
  • 以上所有观点都指向同一个结论,即整体系统将具有更长的寿命,并且从长期来看成本更低。

缺点

  • 这是一个复杂的架构,需要对质量软件原则(例如 SOLID、架构级别的解耦等)有深刻的理解。实施此类解决方案的任何团队几乎肯定需要专家 (YOU) 来推动解决方案并使其远离发展错误的方式并积累技术债务。
  • 清洁 DDD 需要更多的仪式,而不仅仅是编写由单个项目组成的简单的单片 3 层应用程序。
  • 增加了支持架构的前期复杂性,这可能需要使用额外的工具包来最大程度地减少痛苦,例如 AutoMapper 和 MediatR。
  • 这种架构通常不适合简单的 CRUD 应用程序,并且可能会使此类解决方案过于复杂。相反,可以说 CRUD 是不自然的,违背人性,并迫使业务团队改变他们的行为以适应软件系统,而不是相反。但这是一个不同的讨论...
  • 改变(如改变主意)是困难的。试图获得管理层和其他团队成员的支持可能需要大量的说服力。
  • 总之,这种方法的前期成本更高。你需要决定在你的情况下是否值得。

高级主题

首先,命令与查询的分离允许您将模块一直拆分到数据库。在极端架构中,可能有一个仅用于命令的数据库和一个或多个仅用于读取的单独数据库。此外,可以对读取的数据库进行非规范化,这可以极大地提高性能和可伸缩性。我目前无意在演示应用程序中实现这种架构。

CQRS 的极端逻辑结论导致了一种称为事件溯源的架构模式,这本质上意味着状态数据不存储在命令数据库中,而是一系列事件,这些事件使数据从一些基本的初始化状态发生了变异。通过“重放”事件,可以获得数据的快照,这使您可以从任何时间点获取数据的状态。此快照可以通过最终一致性随时间同步到读取数据库,或其他一些复制模式。这种方法可能适用于某些高级应用程序,例如金融应用程序。出于我们的目的,我可以拥有一个单一的数据库而不是尝试实现事件溯源。您为实现这种高级架构模式付出的代价是显着增加了复杂性。最后,我研究过的大多数专家都同意 CQRS 可以在不使用事件溯源的情况下提供巨大的好处。这是我建议您谨慎行事的另一个领域,因为这些高级模式不适合胆小的人。

结论

在这篇博客文章中,我介绍了 Clean Architecture,它是一种一流的架构,它随着时间的推移从其他几种架构方法发展而来,并首先由 Bob 正式化。然后我讨论了领域驱动设计如何与 Clean Architecture 结合以产生 Clean DDD,这是一种架构方法,它将 DDD 的方法论和以业务为中心与 Clean Architecture 的逻辑分离相结合,以产生优雅且更容易转换为微服务的应用程序. 最后,我介绍了 CQRS,这是一种行为架构模式,它增强了 Clean DDD,从提高性能到更轻松的测试和更好的可扩展性,一切都得到了改善。

来源:

https://www.toutiao.com/a7057052695844274727/?log_from=9f3c53df832bb_1645595473806

“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com

来都来了,走啥走,留个言呗~

 IT大咖说  |  关于版权

由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!

感谢您对IT大咖说的热心支持!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT大咖说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ◆ 整洁的领域驱动设计
  • ◆ 核心层
  • ◆ 领域层
  • ◆ 应用层
  • ◆ 外围层
  • ◆ 持久层
  • ◆ 基础设施层
  • ◆ 表示层
  • ◆ 用户界面
  • ◆ 公共层
  • ◆ CQRS 入门
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档