理解ABP的领域驱动设计

大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。

关于玩转ABP框架相关的文章,之前在博客园陆续写了《ABP vNext系列文章和视频》,大家可以跳转过去看,后续文章首发主要以CSDN为主。

言归正传,ABP 框架的主要目标是为应用程序开发引入一种架构方法,并提供必要的基础设施和工具。

领域驱动设计(DDD) 是 ABP 产品架构的核心内容之一。ABP 代码脚手架是基于 DDD 进行逻辑分层的。包括它的实体、应用服务、存储库、领域服务、领域事件、规约等。
ABP代码逻辑分层
由于 DDD 是 ABP 应用开发架构的核心,因此除了理论部分,我们有必要对实现细节进行深入分析。

一、DDD 核心概念

在我们介绍实现细节之前,让我们先了解 DDD 的核心概念和构建模块。让我们从 DDD 的定义开始。

1.什么是领域驱动设计?

DDD 是一种针对复杂需求的软件开发方法,它适用于复杂领域和大规模应用。对于简单的增删改查(CRUD),您通常不需要遵循所有 DDD 原则。但是,在复杂的应用中遵循 DDD 原则和模式可以帮助您构建灵活、模块化和可维护的代码库。

DDD 关注核心领域逻辑而不是基础设施细节,这些细节通常与业务代码隔离。

DDD 与面向对象编程(OOP) 原则密切相关。本书并未涵盖这些基本原则,但对 OOP 和单一职责、开闭、Liskov 替换、接口隔离和依赖倒置(SOLID) 原则的良好理解仍然会对您有很大帮助。

以上提供了简短的定义,我们先探索 DDD 的基本分层。
DDD

上图显示了各层及其关系:

  • 领域层包含基本的业务对象,它是独立的可重用的领域逻辑。该层不依赖于任何其他层,但所有其他层直接或间接依赖于它。
  • 应用层实现应用操作,通常是用户通过 UI 执行的操作。应用层调用领域层的对象来执行这些操作。
  • 表示层包含应用的UI 组件,例如 Web 应用的视图、JavaScript 和 CSS 文件。它不直接调用领域层或数据库对象。相反,它调用应用层。通常,对于在 UI 上执行的每个用例/操作,应用层都有相应的功能/方法。
  • 基础设施层依赖于所有其他层并实现这些层定义的抽象。它有助于优雅地将您的业务逻辑与第三方库和系统(例如数据库或缓存提供程序)分开。
    以上模型的每一层都有一个职责,并包含各种构建模块。

1.1 DDD相关概念澄清

从技术角度来看,DDD 是主要与您的业务代码有关。业务逻辑分为两层——领域层和应用层。其他层(表示层和基础设施)被视为实现细节。

领域层包含的概念有如下:

  • 实体:实体是具有状态(属性)和业务逻辑的业务对象。一个实体具有一个唯一标识符 (ID),用于将该实体与其他实体区分开来。这意味着具有不同标识符的两个实体被视为不同的实体,即使所有其他属性都相同。
  • 值对象:值对象是另一种类型的业务对象。值对象由它们的状态(属性)标识,它们没有标识符 (ID)。这意味着如果两个值对象的所有属性都相同,则它们被认为是相同的。值对象通常比实体更简单,并且通常不可变的。例如地址、货币或日期。
  • 聚合聚合根:聚合是由聚合根组织起来的一组对象(实体和值对象)集合。聚合根负责管理和协调实体对象。
  • 存储库:存储库是一个类集合的接口,领域和应用层使用它来访问持久化系统。它隐藏了数据库提供者的复杂性。
  • 领域服务:领域服务是实现核心业务规则的无状态服务(类)。它的实现依赖于多种聚合(当这些聚合都不能实现逻辑的时候)或外部服务。
  • 规约:规约是一个可重用、可测试和可组合的Lamda过滤器,用于业务规则的封装和抽象。
  • 领域事件:领域事件是一种以松散耦合的方式通知其他服务的通知。它对于连接跨多个聚合很有用。

应用层包含以下概念:

  • 应用服务:应用服务是实现业务应用的无状态服务(类)。它通常获取和返回数据传输对象,其方法被表示层调用。它通过编排领域层对象来执行特定的业务。业务通常表现为事务(原子)过程。
  • 数据传输对象(DTO):DTO 用于在表示层和应用层之间传输数据(状态)。它不包含任何业务逻辑。
  • 工作单元(UOW):UOW 是事务边界。UOW 中的所有状态更改(通常是数据库操作)必须以原子方式实现,成功时一起提交,失败时一起回滚。
    了解并熟悉 DDD 的核心概念很重要,这也是我在这里简要介绍它们的原因。

2.构建基于 DDD 的 解决方案

我们已经介绍了基于 DDD 的分层和解决方案的核心模块。接下来我们了解如何基于 DDD 对 .NET 解决方案进行分层。先从最简单的解决方案结构开始。然后解释 ABP 解决方案的启动模板是如何演变成现在的结构的。最后,您将了解为什么 ABP 启动解决方案内部有这么多项目以及每个项目的用途。

2.1 创建一个简单的基于 DDD 的 解决方案

让我们从头开始,让我们看一下Visual Studio 中基于 DDD 的简单 .NET 解决方案,如以下屏幕截图所示:
在这里插入图片描述
假设我们正在构建客户关系管理(CRM) 解决方案,Acme是我们的公司名称,Crm是本示例中的产品名称。我为每一层创建了一个单独的 C# 项目。.NET 项目非常适合分层,因为它们可以将代码库物理分离到不同的包中。同一个项目中的类/类型可以相互引用。但是,跨项目则不行,除非您引用另一个项目来明确定义依赖关系。
下图展示了项目之间依赖关系
在这里插入图片描述
图中实线表示开发时依赖关系,而虚线表示运行时依赖关系。我将在本节后面解释差异。

要理解这些依赖关系,我们需要知道这些项目可能包含什么类型的组件。

  • Acme.Crm.Domain项目包含一个Product类(聚合根实体)和一个IProductRepository接口(存储库抽象)。Product表示一个产品,并具有一些属性,例如IdNamePriceIProductRepository有一些方法可以对产品执行数据库操作,例如InsertDeleteGetList
  • Acme.Crm.Infrastructure项目包含将实体CrmDbContext映射到数据库表的类(EF Core 数据上下文) 。Product它还包含实现IproductRepository接口的EfProductRepository类。
  • Acme.Crm.Application项目包含ProductAppService(应用服务),以及一些用于增删改查的方法。该服务在内部使用IProductRepository接口和Product实体。
  • [Acme.Crm.Web]是一个 [ASP.NET] Core MVC (Razor Pages) Web 应用。它有一个Products.cshtml页面(和一个相关的 JS 文件),负责在 UI 上呈现和管理(增删改查)产品。

Acme.Crm.Web项目还有一个依赖项:Acme.Crm.Infrastructure。它不直接使用该项目中的任何类,因此开发时不需要直接依赖。但是,在运行时需要基础设施层才能使用数据库。
以上是基于 DDD 的解决方案的简约分层。接下来,我们将使用该解决方案并解释 ABP 的启动解决方案是如何演变的。

2.2 ABP启动方案的演进

ABP 默认的启动解决方案比上图所示的解决方案更复杂。如下截图:
在这里插入图片描述
我们从头开始梳理,中间是怎么一步步演化过来的:

2.1.1 EntityFrameworkCore 项目介绍

简约版的 DDD 解决方案包含Acme.Crm.Infrastructure项目,它实现了所有基础设施抽象和集成。而ABP 解决方案有一个专用的基础设施项目 Acme.Crm.EntityFrameworkCore,因为我们认为为对于数据库集成,单独分离出来是一种更好的设计。

当然,基础设施层可以拆分为多个项目。目前ABP 启动模板唯一的基础设施项目是Acme.Crm.EntityFrameworkCore。随着解决方案增长,您可以创建其他额外的基础设施项目。

随着这一变化,最初的基于 DDD 的极简解决方案将如下所示:
在这里插入图片描述
就基础设施层来说,目前的这种改变是微不足道的。

2.1.2 应用层介绍

Acme.Crm.Application项目包含应用服务类,Acme.Crm.Web项目通过引用Acme.Crm.Application来消费这些服务。

大家思考一下这样引用有没有什么问题?

这种设计有一个问题:Acme.Crm.Web间接引用了Acme.Crm.Domain(通过Acme.Crm.Application)。间接依赖具体实现会将领域层中的业务对象(如实体、领域服务和存储库)暴露给表示层,这打破了抽象和实现真正的分层。
在这里插入图片描述

所以,ABP 启动模板将应用层分为两个项目:

  • Acme.Crm.Application.Contracts,其中包含应用服务接口(例如IProductAppService)和相关的 DTO(例如ProductCreationDto)。
  • Acme.Crm.Application,其中包含应用服务的实现(例如ProductAppService)。

为应用服务引入合约(接口)有两个优点:

  • UI 层(Acme.Crm.Web)依赖于服务契约而不依赖于实现,因此也无需依赖于领域层。
  • 可以与客户端程序共享Acme.Crm.Application.Contracts项目,依赖相同的服务接口并重用相同的 DTO 类,而无需共享您的业务层。

官方的 EventHub 解决方案采用了这种设计,并在 UI 和 HTTP API 应用之间重用了Application.Contracts项目,通过这种方式,它可以轻松设置分层架构,其中应用层和表示层托管在不同的应用程序中,但共享服务契约接口。

分离后,当前的解决方案结构将如下图所示:
在这里插入图片描述

采用这种新设计,项目依赖关系图将如下图所示:
在这里插入图片描述

Acme.Crm.Web项目现在只依赖于Acme.Crm.Application.Contracts项目,并且应该始终使用应用服务接口来执行用户交互。

目前,Acme.Crm.Web仍然依赖于Acme.Crm.ApplicationAcme.Crm.EntityFrameworkCore,因为我们在运行时需要它们。我用虚线绘制了这些依赖关系,以表明这些依赖关系不是最佳设计,但现在是必要的。

大家可以思考以下如何摆脱上面的这种依赖,实现更好的设计?

我们将在后面的“将宿主(Hosting)与 UI 分离”部分中介绍我们如何摆脱这些依赖。

2.1.3 领域共享项目介绍

一旦我们分离出契约,我们就不能再在契约项目中使用领域层的对象,因为它们没有对领域层的引用。乍一看,这似乎不是问题,无论如何,我们不应该在应用服务契约中使用这些实体和其他业务对象——我们应该使用 DTO。

但是,请大家思考:假如我们仍然希望重用领域层中的某些类型或值呢?

例如,我们可能希望在 DTO 类中重用枚举ProductType或常量值。但我们也不想从 Acme.Crm.Application.Contracts 项目中添加对Acme.Crm.Domain项目的引用。

解决方案是:引入一个新项目来存放此类类型和值。我们将这个新项目命名为Acme.Crm.Domain.Shared,因为这个项目将成为领域层的一部分并与其他项目共享。这个项目在项目中可能不会包含这么多类型,但我们仍然不想复制代码。

随着Acme.Crm.Domain.Shared项目的引入,新的解决方案结构如下:
在这里插入图片描述

下图显示项目之间的依赖关系:
在这里插入图片描述

Acme.Crm.DomainAcme.Crm.Application.Contracts项目共享新的Acme.Crm.Domain.Shared项目。解决方案中的其他项目也都可以直接或间接地使用该新项目中的类型。

至此,ABP 启动解决方案的基础分层已经完成。接下来我们继续探讨剩下的三个项目。

2.1.4 HTTP API 层介绍

ABP 启动解决方案有两个和HTTP 相关的项目。

第一个是Acme.Crm.HttpApi项目,包含API 控制器(即 REST API)。这个项目将 API 与 UI 分离,同时方便它们在其他场景中被重用。
第二个是Acme.Crm.HttpApi.Client,您可以使用此项目来从客户端应用程序(可以是自己的或第三方 .NET 客户端)使用您的 HTTP API。它使用 ABP 的动态 C# 客户端代理系统,这个在后续会专题讨论。

通过为 HTTP API 层添加两个新项目,我们现在在解决方案中有八个项目,如下图所示:
在这里插入图片描述

下图显示了添加这些新项目后的新依赖关系图:
在这里插入图片描述

Acme.Crm.HttpApiAcme.Crm.HttpApi.Client项目依赖于Acme.Crm.Application.Contracts项目,因为服务器和客户端共享相同的契约接口。Acme.Crm.Web项目依赖于Acme.Crm.HttpApi项目,因为它在运行时提供 API。

废弃 HTTP API 层

并非每个应用程序都需要 HTTP API(即 REST API)。在这种情况下,您甚至可以从解决方案中删除该项目。此外,如果您愿意,可以将 API 控制器移至Acme.Crm.Web项目并丢弃Acme.Crm.HttpApi项目。

下一节将解释解决方案中的最后一个项目。

2.1.5 了解数据库迁移项目

上图中,还有一个名为Acme.Crm.DbMigrator的项目。这是一个控制台应用程序,可用于将实体迁移应用到数据库。它是一个工具项目,而不是基本解决方案的一部分,因此无需在此处研究其详细信息。

2.1.6 测试项目

test除了这九个项目之外,该文件夹下的解决方案中还有六个项目。它们是为每一层单独配置的单元/集成测试项目。其中之一 (Acme.Crm.HttpApi.Client.ConsoleTestApp) 演示了如何使用Acme.Crm.HttpApi.Client调用 HTTP API。其他可以自行探索它们。

3 将宿主与 UI 分离

启动模板的架构模型中有一件令人讨厌的事情是Web项目引用了ApplicationEntityFramework项目。实际上,Web项目中的所有页面/类都没有直接使用这些项目中的类。但是,由于Web项目是运行应用程序的项目,因此我们需要引用这些项目以使它们在运行时可用。

这种结构不是什么大问题,只要你不泄露你的领域和数据库层对象到表示(Web)层即可。

如果您担心泄露并且不想在运行时设置开发时的依赖项,该怎么办?

可以再添加一个项目Acme.Crm.Web.Host,如下图所示:
在这里插入图片描述

通过此更改,[Acme.Crm.Web] 项目成为类库项目,而不是最终应用程序。它仅包含应用程序的表示层页面/组件;它不包含Startup.csProgram.csappsettings.json文件。Acme.Crm.Web.Host项目通过在运行时将所有项目组合在一起负责托管。它不包含任何应用程序 UI 页面或组件。

我觉得这个设计更好。它从 UI 层优雅地提取托管配置详细信息,删除运行时依赖项,并使其更加专注。目前,我们没有在 ABP 启动模板中分离托管应用程序,因为大多数开发人员已经发现 ABP 启动模板很复杂。我相信让项目职责更单一,代码更少,比将所有东西都放在一个地方的单个项目更好。

最后总结下,在本文中,我们了解了每个项目在 ABP 启动模板中的角色,相信您在开发解决方案时会更加自如。在下一篇中,我们将从 DDD 的角度简要回顾 EventHub 解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/753182.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电路仿真王者之争:SmartEDA如何领跑业界,打破传统仿真软件格局?

在电子设计领域,电路仿真软件一直扮演着至关重要的角色。它们为工程师们提供了一个虚拟的实验室,可以在不耗费大量实际资源的情况下,进行电路设计、优化和测试。在众多电路仿真软件中,SmartEDA以其独特的优势,逐渐崭露…

嵌入式开发十九:SysTick—系统定时器

在前面实验中我们使用到的延时都是通过SysTick进行延时的。 我们知道,延时有两种方式:软件延时,即CPU 循环等待产生的,这个延时是不精确的。第二种就是滴答定时器延时,本篇博客就来介绍 STM32F4 内部 SysTick 系统定时…

浅谈API生态建设:API安全策略的6项原则

API作为连接系统与应用的桥梁,在助力实现高效业务流程的同时,也不可避免出现资产管理困难、敏感数据泄漏风险骤增等安全问题。前段时间,安全公司Fastly公布了一项重磅调查报告,报告中显示95%的企业在过去1年中遭遇过API安全问题。…

AXI接口简介

AXI接口,全称为Advanced eXtensible Interface,是ARM公司推出的一种高性能、低成本、可扩展的高速总线接口。AXI接口是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)高级微控制器总线架构的一部分。2003年发布了…

简易电阻、电容和电感测量仪-FPGA

通过VHDL语言编写程序用于设计电阻、电容和电感测量仪,通过使用试验箱进行验证是否设计正确,资料获取到咸🐟:xy591215295250 \\\或者联系wechat 号:comprehensivable 设计并制作--台数字显示的电阻、电容和电感参数测试…

07-border布局的另一个用处

07-border布局的另一个用处 实现如下的布局: 分析: 1.USERNAME和PASSWORD使用form 2.PASSWORD的文本框使用NewMultiLineEntry 布局1 USERNAME和PASSWORD作为一个form整体,使用border布局,form设置为top,文本框设置为center参数。 packa…

Postman 接口测试 安装使用教程

1 下载官网:https://www.postman.com/downloads/ 2 方便下载,特提供百度云网盘: 链接:Postman 3 windows10 安装,点击安装包 #自动安装,并打开 4 举例,比如豆瓣,get 查询时间,图片登 5 举例&#xff0…

HSRP热备份路由协议(VRRP虚拟路由冗余协议)配置以及实现负载均衡

1、相关原理 在网络中,如果一台作为默认网关的三层交换机或者路由器损坏,所有使用该网关为下一跳的主机通信必然中断,即使配置多个默认网关,在不重启终端的情况下,也不能彻底换到新网关。Cisco提出了HSRP热备份路由协…

传神论文中心|第14期人工智能领域论文推荐

在人工智能领域的快速发展中,我们不断看到令人振奋的技术进步和创新。近期,开放传神(OpenCSG)社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自的论…

【干货】一文讲清楚社群裂变的主要模式和SOP流程

一、社群裂变的主要模式 社群裂变是一种依赖于现有成员的推广以吸引新成员的增长策略。以下是几种主要的社群裂变模式: 老带新裂变 定义:通过老用户带动新用户,同时给予某一方或双方奖励的一种裂变形式。 示例:任务宝活动&…

【精选】数据治理项目实施(合集)06——数据标准在数据治理中的落地实践

导读 本文对数据标准管理进行了深入探讨。重点介绍了数据标准的定义,实施路线和具体标准定义的内容,并总结了企业开展数据标准管理面临的常见问题,由于编写的水平和时间有限, 难免有所纸漏, 欢迎大家批评指正。 在现实…

填报高考志愿时,学校、专业和城市怎么选择呢?

我的观点是: 专业>城市>学校 专业是兴趣导向,符合自己的价值观,失去了这种驱动力的专业学习,会变得非常艰难的,而且没有竞争力,所以我的排序第一位是专业。 其次是城市,最好是一线城市&…

OpenAI发布新模型CriticGPT:利用GPT优化GPT训练,RLHF实现超越人类能力!

目录 01 基于GPT-4,改进GPT-4 02 CriticGPT取得了哪些成果呢? 03 RLHF的上限不再是人类 近日,OpenAI突然发布了一个新模型!这个模型基于GPT-4训练,旨在帮助下一代GPT的训练。 CriticGPT能够在代码挑错中找到超过75%…

golang生成RSA公钥和密钥

目录 场景 场景一:加密、解密 场景二:微信退款 场景三:SSL证书 为什么是.key和.pem格式的文件 生成密钥、公钥 密钥、公钥保存到文件中 第一个:保存密钥到文件里 第二个:保存公钥到文件里 场景 场景一&#…

ForkJoinPool浅析

一,概述 相比传统的线程池ExecuteService,ForkJoinPool的优势在于能采用分治算法、工作窃取算法高效利用CPU资源,如下图 Fork即拆分,Join即合并, 通过将大任务拆分成多个小任务,在多个线程中执行后,合并结果即可得到大任务的结果,经典的例子有归并排序、超大数组求和…

如何保护应用?可快速部署的WAF服务器分享

Web应用攻击是安全事件和数据泄露的主要原因。相关统计表明,超过四分之三的网络犯罪直指应用及其漏洞。为保护数量日益增长的应用安全,Web应用防火墙(WAF)因此而生。本文则聚焦于WAF服务器,了解它的性能与具体的实践应用。   新加坡网络安全…

Linux应急响应靶机 2

一、靶机介绍 应急响应靶机-Linux2 前景需要:看监控的时候发现webshell告警,领导让你上机检查你可以救救安服仔吗!! 1,提交攻击者IP 2,提交攻击者修改的管理员密码(明文) 3,提交第一次Webshell的连接URL(http://xxx.xxx.xxx.…

变“回锅肉”专场的《歌手2024》,是不是高开低走了?

《歌手2024》播出已经过半,似乎出现了高开低走的不妙趋势。 6月26日,《歌手》节目组官宣第八期节目的补位歌手为谭维维,曾主动“请战”的她再次回到了《歌手》舞台,实力歌手加入节目按理说是件好事,却意外并未受到观众…

每天写java到期末考试--复习集合与泛型--6.28

1、定义一个Student类,具有name、sex、age属性,具有getName、setName、getSex、setSex、 getAge、setAge方法和三个参数的构造方法 2、编写一个类,名字为ListDemo,在main方法中做以下工作: 定义一个可以保存Student类型对象的List类型对象list1,然后向list1中放入2个学生:new S…

Web渗透:php反序列化漏洞

反序列化漏洞(Deserialization Vulnerability)是一种在应用程序处理数据的过程中,因不安全的反序列化操作引发的安全漏洞;反序列化是指将序列化的数据(通常是字节流或字符串)转换回对象的过程,如…