ABP入门系列(18)—— 使用领域服务

源码路径: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.总结

这一节没有太难的知识点,我们只需谨记,领域服务和应用服务的区别在于只有领域服务才处理业务逻辑。应用服务作为领域服务的消费方,是很薄的一层。 当然,我们也要记住,过度使用领域服务会导致贫血领域模型(即所有的业务逻辑都位于领域服务中,而不是实体和值对象中)。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang语言零基础入门资料整理

GO语言跟它名字一样是门比较装逼的语言,鲜有菜鸟初学者教程,所以没有语言基础的话,理解起来会有点困难。闲来没事整理了一个GO零基础入门资料。 安装与简介 因为国...

45160
来自专栏Rainbond开源「容器云平台」

搬运向 | 浅析serverless架构与实践

1.2K50
来自专栏Golang语言社区

【Go 语言社区】在 Go 语言中,如何正确的使用并发

Glyph Lefkowitz最近写了一篇启蒙文章,其中他详细的说明了一些关于开发高并发软件的挑战,如果你开发软件但是没有阅读这篇问题,那么我建议你阅读一篇。这...

36690
来自专栏程序人生

再谈 API 的撰写 - 契约

现代社会是个契约社会,生活中大大小小的事情都在和契约打交道。去奥莱买件衣服,一纸小票,便是你跟商家的契约:你花钱买到了产品,产品的问题商家会承诺处理(退换货)。...

38980
来自专栏大数据挖掘DT机器学习

如何用R语言从网上读取多样格式数据

第一部分:数据信息 生活中,我们面临着各种各样的数据:比如你的成绩单,比如公司的财务报表,比如朋友圈的一些状态,比如微信里的一段语音……我们生活的大数据时代的一...

35950
来自专栏大数据和云计算技术

对象存储入门

10.5.3 对象接口 对象存储系统(Object-BasedStorage System)是综合了NAS和SAN的优点,同时具有SAN的高速直接访问和NAS...

85040
来自专栏CSDN技术头条

架构之路(六):把框架拉出来

【编者按】本文作者自由飞,具有 传奇般的人生经历: 98年读大学-国际贸易专业 03年11月英语培训机构当英语老师 04年2月-05年6月律师...

19890
来自专栏老码农专栏

一场版本升级引发的性能血案的追凶过程

13950
来自专栏IT派

用Python画一个中国地图

先来聊聊为什么做数据分析一定要用Python或R语言。编程语言这么多种,Java, PHP都很成熟,但是为什么在最近热火的数据分析领域,很多人选择用Python...

53450
来自专栏IT技术精选文摘

一场版本升级引发的性能血案的追凶过程

11540

扫码关注云+社区

领取腾讯云代金券