<<ABP框架>> 领域服务

文档目录

本节内容:

  • 简介
  • 例子
    • 创建一个接口
    • 实现服务
    • 使用应用服务
  • 相关论述
    • 为什么不只用应用服务?
    • 如何强制你使用领域服务?

简介

领域服务(或服务)用来执行领域操作和业务规则。Eric Evans描述一个好的服务需要三个特点(在他的DDD书里):

  1. 操作与领域概念(不是一个实体或值对象天生的一部分)相关。
  2. 接口要按照其它领域模型元素来定义。
  3. 操作是无状态的。

与应用服务获取/返回DTO(数据传输对象)不同,领域服务获取/返回领域对象(如实体或值类型)。

领域服务可被应用服务或其它领域服务调用,但不直接被展现层(应用服务是针对它的)使用。

IDomainService 接口和DomainService 服务

ABP定义了IDomainService接口,按约定所有的领域服务都要实现它,实现之后,领域服务被自动暂时的注册到依赖注入系统。

同样,领域服务(随意地)可以从DomainService类继承,因此它可以使用继承得来的日志、本地化、等属性。即使不继承DomainService类,也可以在需要时注入它。

例子

假设我们有一个任务管理系统,并有分配任务给人的业务规则。

创建一个接口

首先,我们为服务定义一个接口(不是必需,但是一个好的实践):

public interface ITaskManager : IDomainService
{
    void AssignTaskToPerson(Task task, Person person);
}

如你所见,TaskManager服务使用领域对象:一个Task和一个Person。命名领域服务有些约定,可以命名为:TaskManager、TaskService、或TaskDomainService...

实现服务

让我们看一下实现:

public class TaskManager : DomainService, ITaskManager
{
    public const int MaxActiveTaskCountForAPerson = 3;

    private readonly ITaskRepository _taskRepository;

    public TaskManager(ITaskRepository taskRepository)
    {
        _taskRepository = taskRepository;
    }

    public void AssignTaskToPerson(Task task, Person person)
    {
        if (task.AssignedPersonId == person.Id)
        {
            return;
        }

        if (task.State != TaskState.Active)
        {
            throw new ApplicationException("Can not assign a task to a person when task is not active!");
        }

        if (HasPersonMaximumAssignedTask(person))
        {
            throw new UserFriendlyException(L("MaxPersonTaskLimitMessage", person.Name));
        }

        task.AssignedPersonId = person.Id;
    }

    private bool HasPersonMaximumAssignedTask(Person person)
    {
        var assignedTaskCount = _taskRepository.Count(t => t.State == TaskState.Active && t.AssignedPersonId == person.Id);
        return assignedTaskCount >= MaxActiveTaskCountForAPerson;
    }
}

此处我们有两个业务规则:

  • 一个Task应当处于活跃状态,才能分配给一个新的Person。
  • 一个Person最多可以分配3个活跃Task

你可能想知道为什么我在第一个检查里抛出一个ApplicationException,在第二个检查里抛出UserFriendlyException(见异常处理),这跟领域服务毫不相干,我这么做只是举个例子,具体怎么做完全取决于你。我认为用户接口必须检查一个Task的状态并且不应该分配给一个Person,我认为这是一个应用错误,应当对用户隐藏。第二个是UI上的检查,我们显示一个具有可读性的错误信息给用户。

使用应用服务

现在,让我们看一下一个应用服务如何使用TaskManager服务:

public class TaskAppService : ApplicationService, ITaskAppService
{
    private readonly IRepository<Task, long> _taskRepository;
    private readonly IRepository<Person> _personRepository;
    private readonly ITaskManager _taskManager;

    public TaskAppService(IRepository<Task, long> taskRepository, IRepository<Person> personRepository, ITaskManager taskManager)
    {
        _taskRepository = taskRepository;
        _personRepository = personRepository;
        _taskManager = taskManager;
    }

    public void AssignTaskToPerson(AssignTaskToPersonInput input)
    {
        var task = _taskRepository.Get(input.TaskId);
        var person = _personRepository.Get(input.PersonId);

        _taskManager.AssignTaskToPerson(task, person);
    }
}

TaskApplicationService使用给定的DTO(输入)和仓储来获取相关的task和person,然后把它们传递给TaskManager(领域服务)。

相关论述

根据以上例子,你可能有些问题想问。

为什么不只用应用服务

也就是说为什么应用服务不实现领域服务里的业务逻辑?

我们可以简单的这么说:它不是应用服务的任务,因为它不是一个使用案例,而是一个业务操作。我们可能在另一个使用案例里使用同一个“分配任务给人员”的领域逻辑,例如:我们在另一个界面上,用某种方式更新task,这个更新包含分配任务给另一个人员;我们可能有两个不同的UI(一个移动应用和一个Web应用)共享相同的领域;或是一个为远程客户端提供分配task操作的Web Api等。

如果你的领域很简单, 只有一个UI、只有一个地方用到分配任务给人员的操作,你可能想跳过领域服务,直接在你的应用服务里实现业务逻辑,这不是DDD的最佳实践,但ABP没有强制你不能使用这种设计。

如何强制你使用领域服务?

应用服务可以简单的这么做:

public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
    var task = _taskRepository.Get(input.TaskId);
    task.AssignedPersonId = input.PersonId;
}

写应用服务的开发人员可能不知道TaskManager的存在,直接把给定的PersonId赋值给AssignedPersonId,所以要如何阻止这么做呢?

关于这个问题,在DDD领域里有很多的论述,也有一些使用模式,我们不准备非常深入,但我们提供一个简单的方式:

我们把Task实体修改成如下所示:

public class Task : Entity<long>
{
    public virtual int? AssignedPersonId { get; protected set; }

    //...other members and codes of Task entity

    public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
    {
        taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
        AssignedPersonId = person.Id;
    }
}

我们把AssignedPersonId的设置器修改成protected,所以在实体类外就不能修改它。添加一个AssignToPerson方法,接受一个Person和一个TaskPolicy。CheckIfCanAssignTaskToPerson方法检查是否可以分配,并在不能分配时抛出一个对应的异常(此处如何实现不重要)。然后应用服务如下所示:

public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
    var task = _taskRepository.Get(input.TaskId);
    var person = _personRepository.Get(input.PersonId);

    task.AssignToPerson(person, _taskPolicy);
}

我们把_taskPolicy注入给ITaskPolicy并传给AssignToPerson方法。至此就没有第二方式能把一个任务分配给一个人员了,我们只能使用AssignToPerson,再也跳不过业务规则。

时间: 2024-10-13 04:50:28

<<ABP框架>> 领域服务的相关文章

领域驱动设计之实体、值对象、领域服务

建立领域模型的第一步就是需要识别出实体.值对象与领域服务. 一.实体 1.实体是领域中需要唯一标识的领域概念.通常在业务中,需要唯一标识与区分的对象并需要持续对它进行跟踪,这样的对象我们认为是实体. 2.如果两个实体所有状态都一样,但如果标识不一样,就是两个不同实体.比如订单对象就应该是实体,就算两个订单的订单日期.订单总额等信息都一样,只要标识不一样,比如订单号,我们就认为它们是不同的实体. 3.实体只保留必要的属性与行为.比如一个客户实体应该保留客户的基本信息,但像国家.省.城市.街道等信息

DDD领域驱动设计之领域服务

1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 3.DDD领域驱动设计之领域基础设施层 什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B也不好,因为很可能会涉及多个实体或者聚合的交互(也可能是多个相同类型的实体),此时就应该吧这些代码放到领域服务中,领域服务其实就跟传统三层的BLL很相似,只有方法没有属性,也就没有状态,而且最好是用动词命名,service为后缀,但是真正到了实践的时候,很多时候是很难区分是领域实体本身实现还是用领域

ABP官方文档翻译 3.4 领域服务

领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在DDD中单纯的服务)用来执行领域操作和业务规则.Eric Evans在他的DDD书中描述了一个好的服务有三个特征: 1. 与领域概念关联的操作,但不是实体或值对象的自然组成部分. 2. 接口的定义依照领域模型的其他元素. 3. 操作是无状态的. 不像应用服务那样获取或返回DTO,领域服务获取或返回领域

三、基础功能模块,用户类别管理——锁、EF并发处理、领域服务、应用服务的划分

在上一章节中,我们处理了MVC多级目录问题,参见<二.处理MVC多级目录问题——以ABP为基础架构的一个中等规模的OA开发日志>.从这章开始,我们将进入正式的开发过程.首先,我们要完成系统的基础设置模块(在后续的功能中,需要大量使用这些基础设置信息).和一般的OA系统不同,在律所OA系统中,用户类别管理是基础模块中非常重要.使用频率非常高的一个基础模块.虽然此功能只是很小的一个字典项设置,但是其中涉及了锁.并发处理.领域服务于应用服务的划分等繁琐问题. UI功能页面介绍(因用户功能未完成,欠缺

领域服务

领域服务 前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈这个知识点的使用. DDD领域驱动设计初探系列文章: C#进阶系列——DDD领域驱动设计初探(一):聚合 C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上) C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下) C#进阶系列——DDD领域驱

应用服务&amp;领域服务

应用服务&领域服务 DDD理论学习系列--案例及目录 1. 引言 单从字面理解,不管是领域服务还是应用服务,都是服务.而什么是服务?从SOA到微服务,它们所描述的服务都是一个宽泛的概念,我们可以理解为服务是行为的抽象.从前缀来看,根据DDD的经典分层架构,它们又隶属于不同的层,应用服务属于应用层,领域服务属于领域层. 应用层(Application):负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序任务.它不包含业务逻辑. 领域层(Domain):负责表达业务概念,业务状态信息以及

C#进阶系列——DDD领域驱动设计初探(六):领域服务

前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈这个知识点的使用. DDD领域驱动设计初探系列文章: C#进阶系列——DDD领域驱动设计初探(一):聚合 C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上) C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下) C#进阶系列——DDD领域驱动设计初探

四、领域服务、工厂、资源库

一.领域服务 在领域模型中,我们使用实体和对象来描述事物,但有的时候我们要描述的东西它可能并不是事物,也许就是一个过程或者行为,这时候领域服务就起到作用了. 在DDD中,将领域服务定义为“当某个操作不适合放在聚合和值对象的时候,最好的方式就是使用领域服务”. 上面这句话显得有些晦涩,我们回想一下在OOP当中的情况. 我们建立了多个Java对象,每个对象包含自己的业务逻辑.但是,经常的我们需要多个Java对象的合作才能完成一个完整的行为. 或者,有时候我们就是把一个对象转换成另一对象而已,并没有什

如何运用领域驱动设计 - 领域服务

原文:如何运用领域驱动设计 - 领域服务 目录 概述 什么是领域服务 从实际场景下手 更贴近现实 领域服务VS应用服务 扩展上面的需求 最常见的认证授权是领域服务吗 使用领域服务 不要过多的使用领域服务 不要将过多的行为都给了领域服务 总结 小彩蛋 概述 本文将介绍领域驱动设计(DDD)战术模式中另一个非常重要的概念 - 领域服务.在前面两篇博文中,我们已经学习到了什么是值对象和实体,并且能够比较清晰的定位它们自身的行为.但是在某些时候,你会发现某一些业务行为好像不容易落到单个实体或者值对象身上