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. lua 数据类型

    lua 数据类型 8 种数据类型 类型 说明 nil 空类型 boolean 布尔类型 number 数值型, 浮点型 string 字符串 function 函数 userdata 用户自定义结构 ...

  2. Windows 和 Mac 系统下安装git 并上传,修改项目

    首先在MAC上怎么操作. 在gitHub创立一个账户,在创立一个项目,这就不用我说了对吧. 创建完之后是这样的: 接下来,我们打开https://brew.sh 这是下载homebrew的网站,hom ...

  3. AngularJS入门教程:日期格式化

    AngularJS入门教程:日期格式化[转载] 本地化日期格式化: ({{ today | date:'medium' }})Nov 24, 2015 2:19:24 PM ({{ today | d ...

  4. windows的bat脚本

    一个小小的设置固定ip和关闭防火墙的脚本: @echo //-=-=-=-=-=-=-=-=-=-=-=-=-=-=@echo // [固定设置]@echo // 设置IP,子网掩码,网关@echo ...

  5. Transform java future into completable future 【将 future 转成 completable future】

    Future is introduced in JDK 1.5 by Doug Lea to represent "the result of an asynchronous computa ...

  6. javascript 函数和作用域(闭包、作用域)(七)

    一.闭包 JavaScript中允许嵌套函数,允许函数用作数据(可以把函数赋值给变量,存储在对象属性中,存储在数组元素中),并且使用词法作用域,这些因素相互交互,创造了惊人的,强大的闭包效果.[upd ...

  7. 关于SVN工具的配置及使用

    一.在Ubuntu下的配置 1.检测svn是否已经安装过 使用命rpm -qa | grep subversion,如果是一下结果,说明系统已经安装过svn了 如果什么都没有输出,则说明没有安装过sv ...

  8. java图片上传(mvc)

    最近有开始学起了java,好久没写文章了,好久没来博客园了.最近看了看博客园上次写的图片上传有很多人看,今天在一些篇关于java图片上传的.后台接收用的是mvc.不墨迹了,直接上图. 先看目录结构.i ...

  9. Unity C# 一些关于Camera的心得!

    本文原创,转载请注明出处:http://www.cnblogs.com/AdvancePikachu/p/6856374.html 首先,总结了下最近工作中关于摄像机漫游的功能, 脚本如下: Tran ...

  10. JAVA的Executor框架

    Executor框架分离了任务的创建和执行.JAVA SE5的java.util.concurrent包中的执行器(Executor)管理Thread对象,从而简化了并发编程.Executor引入了一 ...