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返回给展现层.因此,展现 ...
随机推荐
- javascript继承详解(待续)
常见继承分两种,一种接口继承,继承方法签名:一种实现继承,继承实际方法.js只支持后一种. 1原型链 首先看原型.构造函数.实例的关系.如果我们让一个函数的原型对象等于另一个的实例,然后另一个的原型对 ...
- 用 js 的 selection range 操作选择区域内容和图片
原创文章,转载请注明出处并保留地址.原文地址:http://www.cnblogs.com/muge10/p/6723894.html 最近在做编辑器相关的东西,遇到一个需求,用户在编辑器中插入或者粘 ...
- jdk源码剖析五:JDK8-废弃永久代(PermGen)迎来元空间(Metaspace)
目录 1.背景 2.为什么废弃永久代(PermGen) 3.深入理解元空间(Metaspace) 4.总结 ========正文分割线===== 一.背景 1.1 永久代(PermGen)在哪里? 根 ...
- js表白心形特效
好久没有仔细钻研技术了,闲下来借鉴一下做出一些效果 友情链接: http://tiepeng.applinzi.com/love_you/ ;;background:#ffe;font-size:12 ...
- 我眼中的微信小程序
开始关注微信小程序是从去年8月份开始,当时它还没这么"火",而且当时我个人对其的发展也并不看好. 其一:是因为微信是第三方软件,把我的用户数据和信息挂载在微信上这样真的可靠吗?有朋 ...
- 【BZOJ1001】[BeiJing2006]狼抓兔子
挺简单一个题,最小割模板 我的感觉就是可能建图的时候会比较麻烦吧,毕竟三个方向. #include <cctype> #include <climits> #include & ...
- oracle AWR性能监控报告生成方法
目前相当一部分公司会用到oracle,在做性能测试的时候,对数据库的监控很重要,那么这里先介绍下如何生成oracle自带的awr监控报告,而具体报告的内容分析会放在后续的博客中 oracle性能分析入 ...
- OC中Foundation框架之NSString、NSMutableString
创建方式 )直接赋值 NSString *str =@"abc"; )创建对象 NSString *str2 = [[NSString alloc]init]; str2 =@&q ...
- TPshop中B2C与B2B2C的一点理解
首先来一段百度百科记录一下: B2C 是Business-to-Customer的缩写,而其中文简称为"商对客"."商对客"是电子商务的一种模式,也就是通常说的 ...
- 工具类总结---(四)---Sharedpreferences保存
用于保存具有对应关系的键值对 import android.content.Context; import android.content.SharedPreferences; import java ...