本文档详细介绍了 foodtruacker,这是一个实施领域驱动设计 (DDD)、CQRS 和事件溯源的项目。它使用 ASP.NET Core,专注于提高复杂业务领域的可维护性。该项目使用一个简化的虚构业务案例来进行说明。这个详细的解释涵盖了动机、特征、实现细节和相关技术。
foodtruacker - DDD、CQRS 和事件溯源的实施
这个事件驱动的项目利用了原则、框架和架构——所有这些都围绕着在处理反映复杂业务领域的系统时增强可维护性的想法。该应用程序的 Web API 基于 Microsoft 的 ASP.NET Core 框架构建,并实现域驱动设计以及 CQRS 和事件源模式。一个虚构的商业案例奠定了该项目的基础,并且是事件风暴研讨会的结果。
请注意:该项目引入的虚构业务领域已被大大简化,只能被视为相关用例的提供者。
动机
由于在业务领域相当复杂的项目中使用 CRUD 操作和 POCO 对象并不总是最好的,因此我决定创建这个项目作为我对领域驱动设计 (DDD) 的研究和兴趣的实际实现开发软件的方法。
由于该项目引入的虚构业务案例很大程度上是事件驱动的,因此我决定还实现 CQRS 和事件溯源模式。在为这个项目进行研究时,两者都引起了我的注意,并且与 DDD 配合得很好。
特征
概述
该项目由一个可执行的 Web API 应用程序和几个功能组件组成,每个组件都通过类库提供。代码按名称空间组织。在 Visual Studio 中创建的 ASP.NET Core 应用程序中,默认情况下,命名空间是从项目的文件夹结构自动创建的。请参阅下图以了解该项目的文件夹(和命名空间)结构的概览:
介绍
事件风暴
由 Alberto Brandolini 发明的一种灵活的研讨会形式,用于协作探索复杂的业务领域。它是一种极其轻量级的方法,可用于快速改进、设想、探索和设计组织内的业务流程和流程。
该研讨会由一群具有不同专业知识的人员组成,他们使用彩色便利贴协作布置相关业务流程。 EventStorming 研讨会必须有合适的人员在场,并有足够的表面积来放置便签。所需人员通常包括知道要问的问题的人(通常是开发人员)和知道答案的人(领域专家、产品所有者)。
本次研讨会的目的是让参与者互相学习、揭示和反驳误解,并为开发反映相关业务领域的基于事件的软件解决方案奠定基础(例如在这个 GitHub 项目中)。
领域驱动设计(DDD)
一种软件开发方法,其开发集中于对域模型进行编程,该域模型对相关业务域的流程和规则有丰富的了解。 “领域驱动设计”一词是由 Eric Evans 在他的同名书中创造的。
DDD 旨在简化复杂应用程序的创建,并重点关注三个核心原则:
Eric Evans 的书定义了领域驱动设计的一些常用术语:
领域模型
描述业务域的流程和策略并用于处理与该域关联的所需任务的抽象系统。
无处不在的语言
业务领域某些元素的词语和陈述。为了再次避免误解,所有团队成员都应该采用某些术语,通常是领域专家使用的术语。
有界上下文
定义和应用特定领域模型的概念边界。这通常代表一个子系统或一个工作领域。它主要是一种语言界定,每个有界上下文都有自己的通用语言。
例如:客户管理,其中用户称为“客户”。
Eric Evans 的书进一步区分了领域模型的某些部分。仅举几例:
实体
由其身份而不是其属性定义的对象。
例如:一个人永远是同一个人,无论在某个时刻选择什么夹克、头发颜色或说什么语言。
值对象
仅由其属性值定义的对象。值对象是不可变的,并且没有唯一的标识。值对象可以被具有相同属性的其他值对象替换。
例如:当聚焦于一个人时,一副破损的太阳镜可以很容易地用一副外观相同的新太阳镜替换。
总计的
一个或多个实体和可选值对象的集群,统一为单个事务单元。一个实体将构成聚合的基础,因此被声明为聚合根。它的所有协作实体和值对象的属性只能通过这个单一的基础实体来访问。聚合必须始终处于一致状态。在面向对象编程中,这通常是通过使用私有 setter 和受保护 getter 来完成的。
例如:在汽车销售环境中,一辆汽车(实体)由其车辆识别号定义。这辆车可能有四个轮子(值对象),在一定时间后可能需要更换。
领域事件
作为域模型中活动的结果而创建的对象。它用于保存和转发与此活动相关的信息。领域事件通常是为领域专家认为相关的那些活动创建的。
六边形架构(端口和适配器)
一种用于软件设计的架构模式,由 Alistair Cockburn 于 2005 年提出。该模式旨在实现高度的可维护性,并以三层描述应用程序。每层使用接口(端口)和实现(适配器)与相邻层通信:
这种架构模式的关键规则是依赖关系只能指向内部。内圈中的任何事物都无法了解外圈中的任何事物。任何愿意向外指向的依赖项,例如从应用程序层调用数据库,都需要通过控制反转 (IoC) 或依赖项注入 (DI) 进行实例化。
使用 MediatR(预构建的消息传递框架)的 CQRS
CQRS 代表命令/查询责任分离,由 Greg Young 于 2010 年首次提出。它基于命令查询分离 (CQS) 原则,允许分离读写操作。 CQS 规定:
CQRS 相对于 CQS 的改进在于,这些命令和查询被视为模型而不是方法。这些模型可以在某一点作为对象进行分派,然后由系统中另一点所需的相应处理程序进行处理,每个处理程序返回其响应模型,以明确隔离每个操作。
中介模式允许利用中介对象来实现松散耦合的命令/查询和处理程序。对象之间不再直接通信,而是通过中介者进行通信。
MediatR 框架是中介者模式的开源实现,由 Jimmy Bogard 创建。它将在本项目中用于框架层和应用程序层之间的通信。它还将用于将数据从命令数据库投影到查询数据库。
事件溯源
一种架构设计模式,用于存储应用程序状态的每个更改,而不是仅存储域中数据的当前状态。这种模式由 Greg Young 提出,此后被广泛采用。
该模式旨在将应用程序状态的每次更改捕获为事件对象。然后,这些事件对象按照发生的顺序以仅附加的方式存储。这不仅允许在迄今为止发生的事件序列上重新创建对象的当前状态,而且最终允许及时返回并重新创建任何给定时间的对象状态。
银行账户是事件溯源原则的一个很好的例子。每次取款或存入资金时,不仅会更新当前余额,还会记录变化金额。然后通过检查事件序列以及每次提取或存入金额的相应信息来计算当前余额。
事件溯源与领域驱动设计配合得很好,因为它非常适合存储领域事件,由领域模型在每次更改请求时触发。
事件溯源也从 CQRS 中受益匪浅。不必对事件溯源数据库进行查询,事件溯源数据库必须遍历与所请求的对象相关的所有记录事件才能重新创建当前状态,而是可以针对专用查询数据库进行此查询。该查询数据库由其自己的事件处理程序更新,侦听附加到事件源数据库后立即调度的相同事件。这些更新过程称为预测。
这种数据库的分离也为可扩展性和性能优化的巨大潜力奠定了基础。只需让其事件处理程序侦听在应用程序状态发生相关更改后立即从事件源数据库客户端分派的事件,即可创建查询数据库的多个实例并保持同步。数据库类型的选择以及数据非规范化程度、每个查询的优化可以极大地提高性能。
读取模型的这种不断更新可以同步或异步发生。后者是以最终一致性为代价的,读取模型与写入模型在很小的时间间隔(通常是毫秒)内不同步。
共享内核
领域层的通用库,其中包含通用的领域驱动设计特定基类、领域实体、值对象等,这些在有界上下文之间共享。
入门
要按原样启动并运行该项目,请随意执行以下步骤:
先决条件
设置
在浏览器中启动 https://localhost:5001/swagger/index.html 以查看 API 的 Swagger 文档。
使用 Swagger、Postman 或任何其他应用程序将 POST 请求发送到 https://localhost:5001/api/Administration/Register 以注册您的初始管理员帐户。发送以下对象:
查看控制台应用程序或为应用程序日志重新配置的输出。用户成功注册后,应该有一个电子邮件验证链接(由 EmailService 提供)写入日志中。将此网址复制并粘贴到浏览器中,然后按 Enter 键完成注册。请随意更改或基于此不正确的电子邮件服务实施;-)
你已经准备好了。接下来登录。
在浏览器中启动 http://localhost:2113/ 以查看 EventStoreDB GUI。打开“流浏览器”选项卡以查看所有存储的事件。
可以通过运行以下命令来执行测试:
技术
该项目使用以下技术/NuGet 包:
资源/推荐阅读
阿尔贝托·布兰多里尼:
https://www.eventstorming.com
沃恩·弗农:
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_1.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_2.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_3.pdf
阿利斯泰尔·科伯恩:
https://web.archive.org/web/20180822100852/http://alistair.cockburn.us/Hexagonal+architecture
罗伯特·C·马丁(鲍勃叔叔):
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
塞萨尔·德拉托雷、比尔·瓦格纳、迈克·鲁索斯:
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/
格雷格·杨
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
https://cqrs.wordpress.com/documents/building-event-storage/
https://msdn.microsoft.com/en-us/library/jj591559.aspx
马丁·福勒:
https://www.martinfowler.com/bliki/CQRS.html
吉米·博加德:
https://github.com/jbogard/MediatR
https://www.youtube.com/watch?v=SUiWfhAhgQw
领域驱动设计:
https://dddcommunity.org
https://thedomaindrivendesign.io
https://dotnetcodr.com/2013/09/12/a-model-net-web-service-based-on-domain-driven-design-part-1-introduction/
https://dotnetcodr.com/2015/10/22/domain-driven-design-with-web-api-extensions-part-1-notifications/
六边形架构:
https://fideloper.com/hexagonal-architecture
https://herbertograca.com/2017/09/14/ports-adapters-architecture/
制作人员
http://www.andreavallotti.tech/en/2018/01/event-source-and-cqrs-in-c/
https://www.exceptionnotfound.net/real-world-cqrs-es-with-asp-net-and-redis-part-1-overview/
https://buildplease.com/pages/fpc-1/
https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/
https://itnext.io/why-and-how-i-implemented-cqrs-and-mediator-patterns-in-a-microservice-b07034592b6d