实现领域驱动设计 - 使用ABP框架 - 领域逻辑 & 应用逻辑
领域逻辑 & 应用逻辑
如前所述,领域驱动设计中的业务逻辑分为两部分(层):领域逻辑和应用逻辑:

- 领域逻辑由系统的核心领域规则组成,应用逻辑实现应用特定的用例
虽然定义很明确,但实现起来可能并不容易。您可能无法决定哪些代码应该位于应用程序层,哪些代码应该位于领域层。本节试图解释其中的差异
多个应用程序层
当系统比较大时,DDD有助于处理复杂性。特别是,如果在一个领域中开发了多个应用程序,那么领域逻辑与应用程序逻辑的分离就变得重要得多。
假设您正在构建一个具有多个应用程序的系统
一个网站应用程序,用 ASP.NET Core MVC 构建,向用户展示你的产品。这样的网站不需要认证就可以看到产品。用户只有在执行某些操作(比如将产品添加到购物车中)时才会登录到网站。
一个后台管理程序,使用 Angular UI 构建(使用REST APIs)。本应用被公司办公人员使用来管理系统(如编辑产品描述)
一个移动应用程序, 与网站相比,它具有更简单的UI。它可以通过 REST APIs 或其他技术(如TCP套接字)与服务器通信。

每个应用程序都有不同的需求、不同的用例(应用服务方法)、不同的dto、不同的验证和授权规则……等
如果将所有这些逻辑混合到单个应用程序层会使您的服务包含太多的逻辑,使代码更难开发、维护和测试,并导致潜在的bug
如果一个领域有多个应用程序:
- 为每个应用程序/客户端类型创建单独的应用程序层,并在这些单独的层中实现应用程序特定的业务逻辑。
- 使用单个领域层共享核心领域逻辑。
这样的设计使得区分领域逻辑和应用程序逻辑变得更加重要。
为了更清楚地了解实现,您可以为每个应用程序类型创建不同的项目(.csproj)。例如:
后台管理应用:
IssueTracker.Admin.Application & IssueTracker.Admin.Application.Contracts公共网站应用:
IssueTracker.Public.Application & IssueTracker.Public.Application.Contracts移动应用:
IssueTracker.Mobile.Application & IssueTracker.Mobile.Application.Contracts
案例
本节包含一些应用程序服务和领域服务示例,以讨论如何决定将业务逻辑放置在这些服务中
示例:在领域服务中新建组织
public class OrganizationManager : DomainService
{
//省略了依赖注入
public async Task<Organization> CreateAsync(string name)
{
if(await _organizationRepository.AnyAsync(x => x.Name == name))
{
throw new BusinessException("IssueTracking:DuplicateOrganizationName");
}
await _authorizationService.CheckAsync("OrganizationCreationPermissin");
Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}");
var organization = new Organization();
await _emailSender.SendAsync(
"admin@issuetracking.com",
"New Organization",
"A new organization created with name: " + name
);
return organization;
}
}
让我们一步一步地看看 CreateAsync 方法,来讨论代码部分是否应该放在领域服务中
正确: 它首先检查重复的组织名称,并在这种情况下抛出异常。这与核心领域规则有关,我们不允许重复名称
错误: 领域服务不应该执行授权。授权 应该在应用层中完成。
错误: 它记录了包含当前用户的用户名的消息。领域服务不应该依赖于当前用户。即使系统中没有用户,领域服务也应该可用。当前用户(会话)应该是一个与表示/应用层相关的概念。
错误: 它发送了关于这个新组织创建的 电子邮件。我们认为这也是一个特定于用例的业务逻辑。您可能希望在不同的用例中创建不同类型的电子邮件,或者在某些情况下不需要发送电子邮件。
示例:在应用服务中新建组织
public class OrganizationAppService : ApplicationService
{
//省略了依赖注入
[UnitOfWork]
[Authorize("OrganizationCreationPermissin")]
public async Task<Organization> CreateAsync(CreateOrganizationDto input)
{
await _paymentService.ChargeAsync(
CurrentUser.Id,
GetOrganizationPrice()
);
var organization = await _organizationManager.CreateAsync(input.Name);
await _organizationManager.InsertAsync(organization);
await _emailSender.SendAsync(
"admin@issuetracking.com",
"New Organization",
"A new organization created with name: " + input.Name
);
return organization; //!!!
}
private double GetOrganizationPrice()
{
return 42.0; //或者从其他地方获取
}
}
让我们一步一步地看看 CreateAsync 方法,来讨论代码部分是否应该放在应用程序服务中
正确: 应用程序服务方法应该是工作单元(事务)。ABP的 工作单元 系统使这个自动完成(甚至不需要为应用服务添加
[UnitOfWork]属性)。正确: 授权 应该在应用层完成。这里,它是通过使用
[Authorize]属性来完成的正确: 调用支付(基础设施服务)来为该操作收费(创建组织在我们的业务中是一种付费服务)
正确: 应用服务方法负责将更改保存到数据库。
正确: 我们可以发送 电子邮件 通知系统管理员
错误: 不要从应用程序服务返回实体。而是返回一个DTO。
讨论:为什么我们不将支付逻辑转移到领域服务中?
您可能想知道为什么支付代码不在 OrganizationManager 中。这是一件很重要的事情,我们不想错过付款。
然而,仅仅重要还不足以将代码视为核心业务逻辑。 我们可能还有其他的用例,在这些用例中创建一个新的 Organization 是不需要收费的。比如:
管理员用户可以使用后台应用程序创建新的组织,而无需支付任何费用
后台工作的数据导入/集成/同步系统也可能需要创建没有任何支付操作的组织。
如您所见,支付不是创建有效组织的必要操作。它是特定于用例的应用程序逻辑。
示例: 增删改查 操作
public class IssueAppService
{
private readonly IssueManager _issueManager;
public IssueAppService(IssueManager issueManager)
{
_issueManager = issueManager;
}
public async Task<IssueDto> GetAsync(Guid id)
{
return _issueManager.GetAsync(id);
}
public async Task CreateAsync(IssueCreationDto input)
{
return _issueManager.CreateAsync(input);
}
public async Task UpdateAsync(UpdateIssueDto input)
{
return _issueManager.UpdateAsync(input);
}
public async Task DeleteAsync(Guid id)
{
return _issueManager.DeleteAsync(id);
}
}
这个应用程序服务本身不做任何事情,而是将所有工作委托给领域服务。它甚至将 dto 传递给 IssueManager
- 不要仅仅为没有任何领域逻辑的简单CRUD操作创建领域服务。
- 永远不要向领域服务传递dto或从领域服务返回dto。
应用程序服务可以直接使用存储库来查询、创建、更新或删除数据,除非在这些操作期间需要执行一些领域逻辑。在这种情况下,创建 Domain Service 方法,但只针对那些真正需要的方法
如果你对领域驱动设计和构建大型企业系统更感兴趣,推荐以下书籍作为参考书:
- "Domain Driven Design" by Eric Evans
- "Implementing Domain Driven Design" by Vaughn
Vernon - "Clean Architecture" by Robert C. Martin
完结
实现领域驱动设计 - 使用ABP框架 - 领域逻辑 & 应用逻辑的更多相关文章
- 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?
前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...
- 实现领域驱动设计 - 使用ABP框架 - 通用准则
在进入细节之前,让我们看看一些总体的 DDD 原则 数据库提供者 / ORM 无关性 领域和应用程序层应该与 ORM / 数据库提供程序 无关.它们应该只依赖于 Repository 接口,而 Rep ...
- 实现领域驱动设计 - 使用ABP框架 - 解决方案概览
.NET解决方案的分层 下图显示了使用ABP的 应用启动模板 创建的Visual Studio解决方案: 解决方案名称为问题跟踪,它由多个项目组成.通过考虑DDD原则以及开发和部署实践,该解决方案是分 ...
- 实现领域驱动设计 - 使用ABP框架 - 存储库
存储库 Repository 是一个类似于集合的接口,领域层和应用程序层使用它来访问数据持久性系统(数据库),以读写业务对象(通常是聚合) 常见的存储库原则是: 在领域层定义一个存储库接口(因为它被用 ...
- 实现领域驱动设计 - 使用ABP框架 - 创建实体
用例演示 - 创建实体 本节将演示一些示例用例并讨论可选场景. 创建实体 从实体/聚合根类创建对象是实体生命周期的第一步.聚合/聚合根规则和最佳实践部分建议为Entity类创建一个主构造函数,以保证创 ...
- 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
目录 系列文章 领域逻辑和应用逻辑 多应用层 示例:正确区分应用逻辑和领域逻辑 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落 ...
- 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已. 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂. 不过,这些文章对于那些初学者而 ...
- .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
.net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
目录 系列文章 数据传输对象 输入DTO最佳实践 不要在输入DTO中定义不使用的属性 不要重用输入DTO 输入DTO中验证逻辑 输出DTO最佳实践 对象映射 学习帮助 系列文章 基于ABP落地领域驱动 ...
随机推荐
- 优化博客Ⅱ-CDN加速
CDN加速 自从有了第一次博客优化经验,我就越发对优化感兴趣了嘿嘿(✧∇✧). 看着博客首页打开时长为1200ms左右,我又开始琢磨有什么办法能再给网站提提速,让访问时间降低到1000ms以下,这时候 ...
- Python语言中进程、线程、协程执行效率分析
- 创建多线程的方式二:实现Runnable接口
/** * 创建多线程的方式二:实现Runnable接口 * 1. 创建一个实现了Runnable接口的类 * 2. 实现类去实现Runnable中的抽象方法:run() * 3. 创建实现类的对 ...
- AI编程:如何编写提示词
这是小卷对AI编程工具学习的第2篇文章,今天讲讲如何编写AI编程的提示词,并结合实际功能需求案例来进行开发 1.编写提示词的技巧 好的提示词应该是:目标清晰明确,具有针对性,能引导模型理解问题 下面是 ...
- zos静态网站托管的使用和原理
本文分享自天翼云开发者社区<zos静态网站托管的使用和原理> 作者:Ywq zos静态网站托管是一种功能强大且方便的功能特性,此特性主要用于将静态网站的文件(例如HTML.CSS.Java ...
- 【忍者算法】从图书馆编目到数组搜索:探索缺失的第一个正整数|LeetCode 41 缺失的第一个正整数
从图书馆编目到数组搜索:探索缺失的第一个正整数 生活中的算法 想象你是一位图书馆管理员,正在整理一排连续编号的图书.这些书应该从1号开始按顺序排列,但是有些编号的书不见了.你的任务是找出第一个缺失的编 ...
- Markdown文件上传到博客图片处理
Markdown文件上传到博客图片处理 在本地编写Markdown文章并准备上传到博客园时,经常会遇到的一个挑战是本地图片无法直接显示,因为它们存储在本地文件系统中.为了解决这个问题,有两种常见的策略 ...
- Romberg 数值积分算法+P3779 题解
Romberg 算法 吊打 Simpson(?) 的且不玄学(没有什么十五倍)的数值积分算法. 缺点是过程复杂一点,但是只体现在证明上,代码很短. 铺垫算法 梯形求积公式 公式 \[\int _a^b ...
- 让 LLM 来评判 | 评估你的评估结果
评估你的评估结果 这是 让 LLM 来评判 系列文章的第三篇,敬请关注系列文章: 基础概念 选择 LLM 评估模型 设计你自己的评估 prompt 评估你的评估结果 奖励模型相关内容 技巧与提示 在生 ...
- Qt QVariant 与 自定义类型转换的方法
Example: 1. 声明自定义类型可用于QVariant,类也能用,也是这样,QT的基本数据类型不用声明就可以用,而且存入是什么类型,拿出来还是什么类型 #include <QMetaTyp ...