基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
系列文章
- 基于ABP落地领域驱动设计-00.目录和前言
- 基于ABP落地领域驱动设计-01.全景图
- 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则
- 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
- 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实现、综合案例实现系列文章,敬请关注!
ABP Framework 研习社(QQ群:726299208)
ABP Framework 学习及实施DDD经验分享;示例源码、电子书共享,欢迎加入!
领域服务
领域服务实现领域逻辑,它:
- 依赖于服务和仓储。
- 需要多个聚合,以实现单个聚合无法处理的逻辑。
领域服务与领域对象一起使用,其方法可以获取和返回实体、值对象、原始类型等。然而,它并不获取/返回DTOs,DTOs属于应用层。
示例:将问题分配给用户
回想一下,我们之前是如何实现将问题分配给用户的
public class Issue:AggregateRoot<Guid>
{
//..
//问题关联的用户ID
public Guid? AssignedUserId{get;private set;}
//分配方法
public async Task AssignToAsync(AppUser user,IUserIssueService userIssueService)
{
var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id);
if(openIssueCount >=3 )
{
throw new BusinessException("IssueTracking:CanNotOpenLockedIssue");
}
AssignedUserId=user.Id;
}
public void CleanAssignment()
{
AssignedUserId=null;
}
}
现在,我们将逻辑迁移到领域服务中。首先,修改 Issue 类:
public class Issue:AggregateRoot<Guid>
{
//...
public Guid? AssignedUserId{get;internal set;}
}
- 在聚合中移除
AssignToAsync
方法(因为需要在对应的领域服务中实现该方法。) - 将
AssignedUserId
属性设置器从私有改为内部internal
,以允许从领域服务中设置它。
接下来,创建一个领域服务 IssueManager
定义方法 AssignToAsync
将指定 Issue
分配给指定用户。
public class IssueManager:DomainService
{
private readonly IRepository<Issue,Guid> _issueRepository;
public IssueManager(IRepository<Issue,Guid> issueRepository)
{
_issueRepository=issueRepository;
}
public async Task AssignToAsync(Issue issue,AppUser user)
{
//获取关联用户处于打开状态问题的数量
var openIssueCount=await _issueRepository.CountAsync(
i=>i.AssingedUserId==user.Id && !i.IsClosed
);
//超过3个,则抛出异常
if(openIssueCount>=3)
{
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit");
}
issue.AssignedUserId=user.Id;
}
}
IssueManager
在构造函数中注入需要的仓储,用于查询分配给用户处于打开状态的Issue。
建议使用
Manager
后缀命名来命名领域服务。
这种设计的唯一问题是:Issue.AssignedUserId
现在是 public
,可以在任何外部类中设置。然而,它不应该是公共的,访问范围应该是程序集内部internal
,只有在同一个程序集(IssueTracking.Domain
)项目中才可以调用。
这个例子的解决方案就是如此,我们认为这很合理:
- 领域层开发者在使用 IssueManager 时,已经熟知领域规则。
- 应用层开发者强制使用 IssueManager,因此无法直接修改实体。
以上我们展示了将问题分配给用户的两种实现方式,两种方式权衡之下,我们更加推荐当业务逻辑需要与外部服务协同工作时,创建领域服务。
如果没有一个充分的理由,我们认为没有必要去为领域服务创建接口,比如:为
IssueManager
创建IIssueManger
接口。
应用服务
应用服务是无状态服务,实现应用程序用例。一个应用服务通常使用领域对象实现用例,获取或返回数据传输对象DTOs,被展示层调用。
应用服务通用原则:
- 实现特定用例的应用逻辑,不能在应用服务中实现领域逻辑(需要理清应用逻辑和领域逻辑二者的区别)。
- 应用服务方法不能返回实体,因为这样会打破领域层的封装性,始终只返回DTO。
示例:分配问题给用户
using System;
using System.Threading.Tasks;
using IssueTracking.Users;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace IssueTracking.Issues
{
public class IssueAppService :ApplicationService.IIssueAppService
{
private readonly IssueManager _issueManager;
private readonly IRepository<Issue,Guid> _issueRepository;
private readonly IRepository<AppUser,Guid> _userRepository;
public IssueAppService(
IssueManager issueManager,
IRepository<Issue,Guid> issueRepository,
IRepository<AppUser,Guid> userRepository
)
{
_issueManager=issueManager;
_issueRepository=issueRepository;
_userRepository=userRepository;
}
[Authorize]
public async Task AssignAsync(IssueAssignDto input)
{
var issue=await _issueRepository.GetAsync(input.IssueId);
var user=await _userRepository.GetAsync(inpu.UserId);
await _issueManager.AssignToAsync(issue,user);
await _issueRepository.UpdateAsync(issue);//没有对issue做任何修改,为什么要更新?在IssueManager中进行了状态修改。
}
}
}
一个应用服务方法通常有三个步骤:
- 从数据库获取关联的领域对象
- 使用领域对象(领域服务、实体等)执行业务逻辑
- 在数据库中更新实体(如果已修改)
当时使用EF Core时,最后的 Update 更新操作并不是必须的,应为有 状态变更跟踪。但是建议显式调用,适配其他数据库提供程序。
示例中 IssueAssignDto
是一个简单的 DTO 类:
using System;
namespace IssueTracking.Issues
{
public class IssueAssignDto
{
public Guid IssueId{get;set;}
public Guid UserId{get;set;}
}
}
学习帮助
围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实现、综合案例实现系列文章,敬请关注!
ABP Framework 研习社(QQ群:726299208)
专注 ABP Framework 学习及DDD实施经验分享;示例源码、电子书共享,欢迎加入!
基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则的更多相关文章
- 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则
目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...
- DDD领域驱动设计之领域服务
1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 3.DDD领域驱动设计之领域基础设施层 什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B ...
- [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店
一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...
- DDD领域驱动设计之领域基础设施层
1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 其实这里说的基础设施层只是领域层的一些接口和基类而已,没有其他的如日子工具等代码,仅仅是为了说明领域层的一些基础 ...
- DDD领域驱动设计:领域事件
1 前置阅读 在阅读本文章之前,你可以先阅读: DDD领域驱动设计是什么 DDD领域驱动设计:实体.值对象.聚合根 DDD领域驱动设计:仓储 MediatR一个优秀的.NET中介者框架 2 什么是领域 ...
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践
目录 系列文章 数据传输对象 输入DTO最佳实践 不要在输入DTO中定义不使用的属性 不要重用输入DTO 输入DTO中验证逻辑 输出DTO最佳实践 对象映射 学习帮助 系列文章 基于ABP落地领域驱动 ...
- 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑
目录 系列文章 领域逻辑和应用逻辑 多应用层 示例:正确区分应用逻辑和领域逻辑 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落 ...
- 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则
目录 系列文章 仓储 仓储的通用原则 仓储中不包含领域逻辑 规约 在实体中使用规约 在仓储中使用规约 组合规约 学习帮助 围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实 ...
随机推荐
- 免费JS甘特图组件dhtmlxgantt
安装 参考:https://docs.dhtmlx.com/gantt/desktop__install_with_bower.html 可使用NuGet.Bower.npm包管理器安装(应用在asp ...
- Codeforces Round #712 (Div. 2)
A. Déjà Vu 题意:就是问能否加上字母a,使得字符串不中心对称 思路:只有一种情况不能加入,就是全部是a,剩下的都可以满足,找a的位置就找哪个字母不是a,然后让它的对称位置是新加的这个a 代码 ...
- CRM系统实现自动化的“三部曲”
在了解CRM系统的自动化的时候,我们先来看一下CRM能干什么. 从上面的流程图我们就可以看出,CRM可以管理售前,售中和售后的整个客户生命周期. 为什么在复杂的客户生命周期中需要自动化呢? 当然是为了 ...
- Docker网络,手把手教你如何实现容器网络相关知识
Docker网络 理解Docker0 清空所有环境 测试 三个网络 #问题:docker 是如何处理容器网络访问的? [root@hsStudy /]# docker run -d -P --name ...
- NumPy之:ndarray中的函数
NumPy之:ndarray中的函数 目录 简介 简单函数 矢量化数组运算 条件逻辑表达式 统计方法 布尔数组 排序 文件 线性代数 随机数 简介 在NumPy中,多维数组除了基本的算数运算之外,还内 ...
- [Linux] Linux命令行与Shell脚本编程大全 Part.2
进程 Linux是多用户系统,多个用户可以在不同地方通过网络连接到一个Linux系统上进行操作 w:显示登录人员信息 date:显示当前日期.时间和时区 up:从开机登录到现在经过的时间 load a ...
- 查看 swappiness 值
Swap的使用频率 发表于 2017-06-02 | 分类于 Linux | 评论数: 通过调整swappiness的值, 可以调整系统使用 swap 的频率 该值越小, 表示越大限度的使用物理 ...
- Ansible触发器-tag标签-忽略错误
触发器 playbook handlers handler`用来执行某些条件下的任务,比如当配置文件发生变化的时候,通过notify触发handler去重启服务. 在saltstack中也有类似的触发 ...
- ELK学习实验016:filebeat收集tomcat日志
filebeat收集tomcat日志 1 安装tomcat [root@node4 ~]# yum -y install tomcat tomcat-webapps tomcat-admin-weba ...
- IDEA 常用快捷键列表【建议收藏】
编辑代码 快捷键 说明 Alt+Enter 导入包.自动变量命名等(万能快捷键) Ctrl+X 删除行 Ctrl+Y 删除当前行 Ctrl+D 复制行 Alt+Shift+Up/Down或Ctrl+S ...