ABP入门系列目录——学习Abp框架之实操演练

源码路径:Github-LearningMpaAbp


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来实现数据库交互。

应用服务作为领域服务的消费方,领域服务是无状态的(领域对象具有状态和行为,而领域服务是用来处理业务逻辑的,它本身是一个行为,所以是无状态的)。领域服务是用来协调领域对象完成某个操作,状态由领域对象保存。

上面也说了,领域对象是具有状态和行为的。那就是说我们也可以在实体或值对象来处理业务逻辑。那我们该如何取舍呢?

一般来说,在下面的几种情况下,我们可以使用领域服务:

  1. 执行某个具体的业务操作。
  2. 领域对象的转换。
  3. 以多个领域对象为输入,返回一个值对象。

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)—— 使用领域服务的更多相关文章

  1. ABP入门系列(3)——领域层创建实体

    这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者去使用. 一.首先来看看ABP体系结构 领域层就 ...

  2. ABP入门系列(19)——使用领域事件

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 最近刚学习了下DDD中领域事件的理论知识,总的来说领域事件主要有两个作用,一是解耦,二是 ...

  3. ABP入门系列(2)——领域层创建实体

    ABP入门系列目录--学习Abp框架之实操演练 这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者 ...

  4. ABP入门系列(4)——领域层定义仓储并实现

    一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中,仓储类要实现IRepository接口,接口定 ...

  5. ABP入门系列(3)——领域层定义仓储并实现

    ABP入门系列目录--学习Abp框架之实操演练 一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中 ...

  6. ABP入门系列目录——学习Abp框架之实操演练

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WE ...

  7. ABP入门系列(13)——Redis缓存用起来

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1. 引言 创建任务时我们需要指定分配给谁,Demo中我们使用一个下拉列表用来显示当前系统的所有用 ...

  8. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

  9. ABP入门系列(5)——创建应用服务

    一.解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务逻辑并且将DTO返回给展现层.因此,展现 ...

随机推荐

  1. linux sed命令就是这么简单

    概述 sed命令是一个面向字符流的非交互式编辑器,也就是说sed不允许用户与它进行交互操作.sed是按行来处理文本内容的.在shell中,使用sed来批量修改文本内容是非常方便的. sed命令的选项 ...

  2. 【模板】二分图最大权完美匹配KM算法

    hdu2255模板题 KM是什么意思,详见百度百科. 总之知道它可以求二分图最大权完美匹配就可以了,时间复杂度为O(n^3). 给张图. 二分图有了边权,求最大匹配下的最大权值. 所以该怎么做呢?对啊 ...

  3. 【HDOJ 1085】数学问题,母函数

    Holding Bin-Laden Captive! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Ja ...

  4. PHP的laravel框架后台实现数据导出excel的功能

    要想在PHP后台实现excel导入导出功能,一种简单有效的方法就是使用phpexcel插件. 要使用phpexcel插件,首先需要下载composer,这个工具是专门用来管理项目中库之间的依赖关系的. ...

  5. pixi.js

    添加基本文件(库文件) 渲染库 pixi.js pixi.lib.js是pixi.js的子集,依赖class.js,cat.js,event_emiter.js文件 pixi.scroller.js ...

  6. Java--向数据库添加txt文件中的批量数据

    大家可能会遇到这样的问题,在做一个项目时需要操作数据库,需要有大量的数据需要导入到数据库中,这部分数据存到了txt文档中(可以把Word文档中的数据弄到txt文档),总不能每一条数据都复制黏贴到数据库 ...

  7. React的学习(上)

    摘要 众所周知,前端三大框架Angular.React.Vue,所以为了跟上时代的步伐,最近开始着手学习React,这时候就发现个大问题,框架一直在更新,提倡的编写语法也在更新,网上有许多教程都是一两 ...

  8. uiautomator+cucumber实现自动化测试

    前提 由于公司业务要求,所以自动化测试要达到以下几点: 跨应用的测试 测试用例可读性强 测试报告可读性强 对失败的用例有截图保存并在报告中体现 基于以上几点,在对自动化测试框架选型的时候就选择了uia ...

  9. 某公司HP-EVA4400数据丢失的恢复方法和恢复全过程

    一.故障描述1.设备清单一台HP-EVA4400控制器(型号:AG638-53011)三台HP-EVA4400扩展柜(型号为AG638-63001),和28块HP-FC磁盘(型号为300G FC硬盘) ...

  10. css3常用方法以及css3选择器

    最重要的 CSS3 模块包括: 选择器 框模型 背景和边框 文本效果 2D/3D 转换 动画 多列布局 用户界面   CSS3 边框   CSS3 边框 通过 CSS3,您能够创建圆角边框,向矩形添加 ...