基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
系列文章
- 基于ABP落地领域驱动设计-00.目录和前言
- 基于ABP落地领域驱动设计-01.全景图
- 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则
- 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
- 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实现、综合案例实现系列文章,敬请关注!
ABP Framework 研习社(QQ群:726299208)
ABP Framework 学习及实施DDD经验分享;示例源码、电子书共享,欢迎加入!
领域逻辑和应用逻辑
正如前面提到的,领域驱动设计中的业务逻辑拆分为两部分:领域逻辑和应用逻辑。

领域逻辑由系统的核心领域规则组成,而应用程序逻辑实现特定于应用程序的用例。
虽然定义很清楚,但实现可能并不容易,常常无法决定哪些代码应该放在应用层,哪些代码应该放在领域层。本节试图解释这些差异。
多应用层
当你的系统很大时,DDD有助于处理复杂性问题。特别是,在一个领域中开发多个应用,那么领域逻辑与应用逻辑的分离就变得更加重要。
假设你正在构建一个有多个应用程序的系统:
- 一个Web应用程序,使用 ASP .NET Core MVC,展示产品给用户。浏览产品,不需要进行身份验证;只有当用户执行某些操作时,比如向购物车中添加产品,才会要求登陆。
- 一个后台管理应用程序,使用Angular UI+ REST APIs构建。此应用由公司办公人员做系统管理,比如:编辑产品描述。
- 一个移动应用程序,和 Web应用程序 相比UI更加简单,通过REST APIs或其他技术(如:TCP/Socket)与服务器通信。

每一个应用都需要解决不同的需求,实现不同用例(应用服务方法),不同DTO,不同验证和授权规则等等。
将所有这些逻辑混合到一个应用层中,将使你的服务包含太多的判断条件和复杂的业务逻辑,使代码更难开发、维护和测试,并导致潜在的Bug。
如果你有单个领域关联多个应用程序:
- 为每个应用程序或客户端创建单独的应用层,在这些单独层中实现特定于应用的业务逻辑。
- 使用单个领域层共享核心领域逻辑。
这样的设计使得区分领域逻辑和应用逻辑变得更加重要。
为了更清楚地实现,可以为每种应用程序类型创建不同的项目(.csproj)。
例如:
- 后台管理应用创建
IssueTracker.Admin.Application和IssueTracker.Admin.Application.Contracts项目 - WEB应用创建
IssueTracker.Public.Application和IssueTracker.Public.Application.Contracts - 移动应用创建
IssueTracker.Mobile.Application和IssueTracker.Mobile.Application.Contracts
示例:正确区分应用逻辑和领域逻辑
本节包含一些应用服务和领域服务示例,讨论如何决定在这些服务中放置业务逻辑。
示例:在领域服务中创建 Organization (组织)
public class OrganizationManager:DomainService
{
private readonly IRepository<Organization> _organizationRepository;
private readonly ICurrentUser _currentUser;
private readonly IAuthorizationService _authorizationService;
private readonly IEmailSender _emailSender;
public OrganizationManager(
IRepository<Organization> organizationRepository,
ICurrentUser currentUser,
IAuthorizationService authorizationService,
IEmailSender emailSender
)
{
_organizationRepository=organizationRepository;
_currentUser=currentUser;
_authorizationService=authorizationService;
_emailSender=emailSender;
}
//创建组织
public async Task<Organization> CreateAsync(string name)
{
//检测是否存在同名组织,存在则抛出异常。
if(await _organizationRepository.AnyAsync(x=>x.Name==name))
{
throw new BusinessException("IssueTracking:DuplicateOrganizationName");
}
//检测是否拥有创建权限
await _authorizationService.CheckAsync("OrganizationCreationPermission");
//记录日志
Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}");
//创建组织实例
var organization = new Organization();
//发送提醒邮件
await _emailSender.SendAsync(
"systemadmin@issuetracking.com",
"新组织",
"新组织名称:"+name
);
//返回组织实例
return organization;
}
}
让我们一步一步来分析 CreateAsync 方法中的代码是否都应该放在领域服务中:
- 正确:组织名重复检测,存在重复名称则抛出异常。该检测与核心领域规则相关,不允许重名。
- 错误:领域服务不应该进行权限验证。权限验证应该放在应用层。
- 错误:记录日志包含当前用户的用户名。领域服务不应该依赖当前用户,当前用户(Session)应该是展示层或应用层中的相关概念。
- 错误:创建新组织发送邮件,我们仍然认为这是业务逻辑。可以能会根据用例来创建不同类型邮件。
示例:在应用层创建新组织
public class OrganizationAppService:ApplicationService
{
private readonly OrganizationManager _organizationManager;
private readonly IPaymentService _paymentService;
private readonly IEmailSender _emailSender;
public OrganizaitonAppService(
OrganizationManager organizationManager,
IPaymentService paymentService,
IEmailSender emailSender
)
{
_organizationManager=organizationManager;
_paymentService=paymentService;
_emailSender=emailSender;
}
[UnitOfWork]
[Authorize("OrganizationCreationPermission")]
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(
"systemadmin@issuetracking.com",
"新组织",
"新组织名称:"+name
);
//返回实例
return organization;
}
private double GetOrganizationPrice()
{
return 42;//Gets form somewhere...
}
}
让我们看看 CreateAsync 方法,一步一步讨论其中的代码是否应该放在应用服务:
- 正确:应用服务方法应该是工作单元,ABP框架工作单元系统自动实现,可以不用添加
[UnitOfWork]特性。 - 正确:权限验证应该放在应用层,可以使用
[Authorize]特性。 - 正确:在我们的业务逻辑中创建组织是付费服务,当前操作调用基础设施服务进行支付操作。
- 正确:应用服务方法负责保存变更到数据库。
- 正确:给系统管理员发送邮件通知。
- 错误:不能返回实体,应该返回DTO。
讨论:为什么我们不应该将支付逻辑放在领域服务中?
你可能想知道为什么付款代码不在 OrganizationManager 里面。这是一件很重要的事情,我们绝不希望付款出错。
然而,业务的重要性并不意味着要将其视为核心业务逻辑。我们可能有其他的支付用例,在这些用例中,我们不收取费用来创建一个新的组织。
例如:
- 后台办公系统中的管理员用户可以创建新组织,不用考虑支付。
- 系统数据导入、整合、同步,也可能需要在没有任何支付操作的情况下,创建组织。
如您所见,支付不是创建一个有效组织的必要操作。它是一个特定于用例的应用逻辑。
示例:CRUD操作
public class IssueAppService
{
private readonly IssueManager _issueManager;
public IssueAppService(IssueManager issueManager)
{
_issueManager=issueManager;
}
public async Task<IssueDto> GetAsync(Guid id)
{
return await _issueManager.GetAsync(id);
}
public async Task CreateAsync(IssueCreationDto input)
{
await _issueManager.CreateAsync(input);
}
public async Task UpdateAsync(UpdateIssueDto input)
{
await _issueManager.UpdateAsync(input);
}
public async Task DeleteAsync(Guid id)
{
await _issueManager.DeleteAsync(id);
}
}
应用服务并没有做任何事情,而是委托给领域服务来处理。只接收DTO参数,并传递给 IssueManger 。
- 不要创建只实现简单 CRUD 操作的领域服务方法,而不带任何领域逻辑。
- 不要传递 DTO 给领域服务,或领域服务方法返回 DTO。
应用服务可以直接使用仓储,实现查询、创建、更新或删除数据,除非执行这些操作时需要处理领域逻辑,这种情况下,创建领域服务方法,但只针对那些真正需要的方法。
不要因为将来可能会需要这些CRUD领域服务方法,就去提前创建这些方法! 当需要时再去创建,并重构现有的代码。由于抽象了应用层,重构领域层不会影响到UI层和其他客户端。
学习帮助
围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实现、综合案例实现系列文章,敬请关注!
ABP Framework 研习社(QQ群:726299208)
专注 ABP Framework 学习及DDD实施经验分享;示例源码、电子书共享,欢迎加入!

基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑的更多相关文章
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
目录 系列文章 领域服务 应用服务 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践 ...
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
目录 系列文章 数据传输对象 输入DTO最佳实践 不要在输入DTO中定义不使用的属性 不要重用输入DTO 输入DTO中验证逻辑 输出DTO最佳实践 对象映射 学习帮助 系列文章 基于ABP落地领域驱动 ...
- (转)EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
.net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...
- EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- 如何领域驱动设计?-实践感悟&总结分享
主要是在开发过程中,个人对于领域驱动设计的实践感悟和总结:也是对新进开发人员的培训资料:希望对关注DDD的童鞋有所帮助. 概述 领域驱动不是纯粹的技术问题,领域建模(建立数据表只是一部分)是领域专家( ...
- DDD 领域驱动设计-如何完善 Domain Model(领域模型)?
上一篇:<DDD 领域驱动设计-如何 DDD?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 阅读目录: ...
- 领域驱动设计和实践(转:http://kb.cnblogs.com/page/112298/)
引言 软件系统面向对象的设计思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础.随着编程语言和技术的发展,各种语言特性层 ...
随机推荐
- MySQL如何快速插入数据
前言: 日常学习和工作中,经常会遇到导数据的需求.比如数据迁移.数据恢复.新建从库等,这些操作可能都会涉及大量数据的导入.有时候导入进度慢,电脑风扇狂转真的很让人崩溃,其实有些小技巧是可以让导入更快速 ...
- 关于有符号数和无符号数的转换 - C/C++
转载自:http://www.94cto.com/index/Article/content/id/59973.html 1.引例: 今天在做了一道关于有符号数和无符号数相互转换及其左移/右移的问题, ...
- Linux上的Shebang符号(#!)
使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗? 本文了将给你简单介绍一下Shebang("#!")这个符号. 首先,这个符号(#!)的名称, ...
- 微信小程序组件设计规范
微信小程序组件设计规范 组件化开发的思想贯穿着我开发设计过程的始终.在过去很长一段时间里,我都受益于这种思想. 组件可复用 - 减少了重复代码量 组件做为抽离的功能单元 - 方便维护 组件作为temp ...
- java中基本数据类型、包装类及字符串之间的相互转换
基本数据类型:不支持面向对象的编程机制(没有属性和方法),即不支持面向对象,之所以提供8中基本数据类型,是为了方便常规数据的处理. 包装类:通过包装类可以将基本数据类型的值包装为引用数据类型的对象,使 ...
- [刷题] 167 Two Sum II
要求 升序数组 找到两个数使得它们相加之和等于目标数 函数返回两个下标值(下标从1开始) 示例 输入:numbers = [2, 7, 11, 15], target = 9 输出:[1,2] 思路 ...
- shell脚本 在后台执行de 命令 >> 文件 2>&1 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面)
命令 >> 文件 2>&1或命令 &>> 文件 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面) # ll >>aaa 2> ...
- Linux命令行欢迎界面美化
默认的SSH命令行登录欢迎界面如下 [c:\~]$ Connecting to 10.x.13.x:22... Connection established. To escape to local s ...
- H5开发基础之像素、分辨率、DPI、PPI
H5开发基础之像素.分辨率.DPI.PPI html5 阅读约 4 分钟 2016-09-03于坝上草原 背景知识: 目前绝大部分显示器都是基于点阵的,通过一系列的小点排成一个大矩形,通过每个小 ...
- 单片机编程时易错总结 20181015 项目:3060-A
3060-A的调试过程中: 20181015 V1.30 A.遇到问题: RS232与LY3023的通信总是自己停止 主程序依旧执行 此版本进行如下修改: 1.RS232用的串口1关闭DMA传送 ...