ABP入门系列(18)—— 使用领域服务
1.引言
自上次更新有一个多月了,发现越往下写,越不知如何去写。特别是当遇到DDD中一些概念术语的时候,尤其迷惑。如果只是简单的去介绍如何去使用ABP,我只需参照官方文档,实现到任务清单Demo中去就可以了,不劳神不费力。但是,这样就等于一知半解。
知之为知之,不知为不知,是知也。知其然知其所以然,方能举一反三嘛。
为了揭开迷惑,最近开始研读《实现领域驱动设计》去学习DDD中的思想,并开了一个DDD专题去记录我学习的成果。欢迎大家关注,共同学习进步并不吝赐教!
后续的文章我会继续保持之前的书写风格,并适当穿插一些对DDD中的概念的理解,来加深对ABP框架的学习。
2.用例分析
用户可以无限创建任务但仅能分配给自己;管理员具有分配任务给他人的权限,任务分配成功后要通知接收人。
我们分析下这个业务用例,其实主要涉及到一个业务操作——任务分配。按照我们传统的思路,在做任务分配这个操作时,就是对任务进行编辑,没有什么特别的地方,通过代码调用应用层服务更新Task实体即可。
//TaskAppService.cs
public void UpdateTask(UpdateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService base class.
Logger.Info("Updating a task for input: " + input);
//获取是否有权限
bool canAssignTaskToOther = PermissionChecker.IsGranted(PermissionNames.Pages_Tasks_AssignPerson);
//如果任务已经分配且未分配给自己,且不具有分配任务权限,则抛出异常
if (input.AssignedPersonId.HasValue && input.AssignedPersonId.Value != AbpSession.GetUserId() )
{
if (!canAssignTaskToOther)
throw new AbpAuthorizationException("没有分配任务给他人的权限!");
else
{
var updateTask = Mapper.Map<Task>(input);
_taskRepository.Update(updateTask);
//发送通知
var message = "You hava been assigned one task into your todo list.";
_smtpEmailSender.Send("ysjshengjie@qq.com", updateTask.AssignedPerson.EmailAddress, "New Todo item", message);
_notificationPublisher.Publish("NewTask", new MessageNotificationData(message), null,
NotificationSeverity.Info, new[] { updateTask.AssignedPerson.ToUserIdentifier() });
}
}
}
以上代码也能满足以上需求,但是这已经违背了ABP分层架构的思想,其实也就是违背了DDD的思想。
应用层不包含业务逻辑,而我们的UpdateTask
方法明显承载了太多的业务,既要检查权限又要发送通知。
那可如何是好?自然是在领域服务去处理这些业务逻辑了。
这里就引入了DDD中的两个概念,应用服务和领域服务,我们有必要先来介绍一下,之后再来用领域服务来改造。
3.应用服务VS领域服务
应用服务对应的是应用层,领域服务对应的领域层。它们之间主要的区别在于是否处理业务逻辑。那这个限制从何而来呢?DDD的分层架构思想。
- 用户接口层(Presentation):提供一个用户界面,实现用户交互操作。
- 应用层(Application):进行展现层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务。它不包含业务逻辑。
- 领域层(Domain):包括业务对象和业务规则,这是应用程序的核心层。
- 基础设施层(Infrastructure):提供通用技术来支持更高的层。例如基础设施层的仓储(Repository)可通过ORM来实现数据库交互。
应用服务作为领域服务的消费方,领域服务是无状态的(领域对象具有状态和行为,而领域服务是用来处理业务逻辑的,它本身是一个行为,所以是无状态的)。领域服务是用来协调领域对象完成某个操作,状态由领域对象保存。
上面也说了,领域对象是具有状态和行为的。那就是说我们也可以在实体或值对象来处理业务逻辑。那我们该如何取舍呢?
一般来说,在下面的几种情况下,我们可以使用领域服务:
- 执行某个具体的业务操作。
- 领域对象的转换。
- 以多个领域对象为输入,返回一个值对象。
4. 使用领域服务
经过上面的分析,很显然我们的用例,使用领域服务来实现更合适。
ABP中定义了IDomainService
接口,按约定所有的领域服务都要实现它,实现之后,领域服务被自动暂时的注册到依赖注入系统。
同样,领域服务也可以从DomainService
类继承,因此它可以使用继承得来的日志、本地化、等属性。
这里,我们定义一个ITaskManager
(Abp中约定俗成的领域服务命名规则,以Manager结尾,当然你也可以自行命名)来定义我们的领域服务,然后实现它。
namespace LearningMpaAbp.Tasks
{
public interface ITaskManager : IDomainService
{
void AssignTaskToPerson(Task task, User user);
}
}
实现的领域服务负责主要的业务逻辑,其中发送通知的业务我定义了一个领域事件去实现。关于领域事件,我们下节再聊。
namespace LearningMpaAbp.Tasks
{
public class TaskManager : DomainService, ITaskManager
{
private readonly IPermissionChecker _permissionChecker;
private readonly IAbpSession _abpSession;
private readonly IEventBus _eventBus;
public TaskManager(IPermissionChecker permissionChecker, IAbpSession abpSession, IEventBus eventBus)
{
_permissionChecker = permissionChecker;
_abpSession = abpSession;
_eventBus = eventBus;
}
public void AssignTaskToPerson(Task task, User user)
{
//已经分配,就不再分配
if (task.AssignedPersonId.HasValue && task.AssignedPersonId.Value == user.Id)
{
return;
}
if (task.State != TaskState.Open)
{
throw new ApplicationException("处于非活动状态的任务不能分配!");
}
//获取是否有【分配任务给他人】的权限
bool canAssignTaskToOther = _permissionChecker.IsGranted(PermissionNames.Pages_Tasks_AssignPerson);
if (user.Id != _abpSession.GetUserId() && !canAssignTaskToOther)
{
throw new AbpAuthorizationException("没有分配任务给他人的权限!");
}
task.AssignedPersonId = user.Id;
//使用领域事件触发发送通知操作
_eventBus.Trigger(new TaskAssignedEventData(task, user));
}
}
}
定义完领域服务,我们直接在应用服务层调用即可。
public void UpdateTask(UpdateTaskInput input)
{
//We can use Logger, it's defined in ApplicationService base class.
Logger.Info("Updating a task for input: " + input);
//获取是否有权限
//bool canAssignTaskToOther = PermissionChecker.IsGranted(PermissionNames.Pages_Tasks_AssignPerson);
//如果任务已经分配且未分配给自己,且不具有分配任务权限,则抛出异常
if (input.AssignedPersonId.HasValue && input.AssignedPersonId.Value != AbpSession.GetUserId())
{
var updateTask = Mapper.Map<Task>(input);
var user = _userRepository.Get(input.AssignedPersonId.Value);
//先执行分配任务
_taskManager.AssignTaskToPerson(updateTask, user);
//再更新其他字段
_taskRepository.Update(updateTask);
////发送通知
//var message = "You hava been assigned one task into your todo list.";
//_smtpEmailSender.Send("ysjshengjie@qq.com", updateTask.AssignedPerson.EmailAddress, "New Todo item", message);
//_notificationPublisher.Publish("NewTask", new MessageNotificationData(message), null,
// NotificationSeverity.Info, new[] { updateTask.AssignedPerson.ToUserIdentifier() });
}
}
更新后UpdateTask
方法已经注释掉了权限检查以及发布通知的业务逻辑,整个方法也讲更清晰。
5.总结
这一节没有太难的知识点,我们只需谨记,领域服务和应用服务的区别在于只有领域服务才处理业务逻辑。应用服务作为领域服务的消费方,是很薄的一层。
当然,我们也要记住,过度使用领域服务会导致贫血领域模型(即所有的业务逻辑都位于领域服务中,而不是实体和值对象中)。
ABP入门系列(18)—— 使用领域服务的更多相关文章
- ABP入门系列(3)——领域层创建实体
这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者去使用. 一.首先来看看ABP体系结构 领域层就 ...
- ABP入门系列(19)——使用领域事件
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 最近刚学习了下DDD中领域事件的理论知识,总的来说领域事件主要有两个作用,一是解耦,二是 ...
- ABP入门系列(2)——领域层创建实体
ABP入门系列目录--学习Abp框架之实操演练 这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者 ...
- ABP入门系列(4)——领域层定义仓储并实现
一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中,仓储类要实现IRepository接口,接口定 ...
- ABP入门系列(3)——领域层定义仓储并实现
ABP入门系列目录--学习Abp框架之实操演练 一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中 ...
- ABP入门系列目录——学习Abp框架之实操演练
ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WE ...
- ABP入门系列(13)——Redis缓存用起来
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1. 引言 创建任务时我们需要指定分配给谁,Demo中我们使用一个下拉列表用来显示当前系统的所有用 ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- ABP入门系列(5)——创建应用服务
一.解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务逻辑并且将DTO返回给展现层.因此,展现 ...
随机推荐
- JavaEE开发之SpringMVC中的路由配置及参数传递详解
在之前我们使用Swift的Perfect框架来开发服务端程序时,聊到了Perfect中的路由配置.而在SpringMVC中的路由配置与其也是大同小异的.说到路由,其实就是将URL映射到Java的具体类 ...
- 读《effective C++》1
条款一:视C++为一个语言联邦 学习C++半个月了,学了他的面向过程编程,面向对象编程(封装性,继承性,多态性),template泛型编程,开始只是觉得C++基础是面向对象,但是学了这么多块开始有点迷 ...
- 主机ping通虚拟机,虚拟机ping通主机解决方法(NAT模式)
有时候需要用虚拟机和宿主机模拟做数据交互,ping不通是件很烦人的事,本文以net模式解决这一问题. 宿主机系统:window7 虚拟机系统:CentOs7 连接方式:NAT模式 主机ping通虚拟机 ...
- Extjs6(特别篇)——项目自带例子main.js拆分详解
本文基于extjs6.0.0 一.拆分代码来看看 1.主页面main是个tab页: 写一些页面的依赖: 标明页面的controller和viewModel Ext.define('Learning.v ...
- JS实现图片不间断滚动
方法一: <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title ...
- hdu1166树状数组
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于 ...
- 设计模式--MVC(C++版)
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式.这种模式用于应用程序的分层开发. Model(模型)-是应用程序中用于处理应用程序数据逻辑的部分.通常模型对象 ...
- 每个国家对应的语言Locale和国家代码对照表
DNN3支持多语言,希望下面的语言代码与对应国家能对你有所帮助 语言代码 国家/ 地区 "" (空字符串) 无变化的文化 af 公用荷兰语 af-ZA 公用荷兰语 - 南非 sq ...
- 山东省第八届ACM省赛游记
Day 1: 凌晨,来了几分兴致,和队友在VJudge上开了一把zoj月赛,WA一发闷一口拿铁,一瓶拿铁 不一会就被喝完了!好气啊!遂开始愉快地打游戏,打着打着,woc,居然3点半了,小睡片 刻,咬上 ...
- 新手上路---Java学习知识梳理
我随笔 我相信也有那么一些人跟我一样,脱下军装后对未来感到迷茫,不知所措甚至怀疑起自己的能力,自身体会的经历过才能明白其中的辛酸!归咎一切:心态,信心,自信!再穷不过乞讨,不死终将出头.好了,闲言碎语 ...