文档目录

本节内容:

简介

领域服务(或服务)用来执行领域操作和业务规则。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 = ; 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,再也跳不过业务规则。

英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Domain-Services

ABP框架 - 领域服务的更多相关文章

  1. ABP开发框架前后端开发系列---(15)ABP框架的服务端和客户端缓存的使用

    缓存在一个大型一点的系统里面是必然会涉及到的,合理的使用缓存能够给我们的系统带来更高的响应速度.由于数据提供服务涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发 ...

  2. ABP框架 - 领域事件(EventBus)

    文档目录 本节内容: EventBus 注入 IEventBus 获取默认实例 定义事件 预定义事件 处理完异常 实体修改 触发事件 处理事件 处理基类事件 处理程序异常 处理多个事件 处理程序注册 ...

  3. 基于DDD的.NET开发框架 - ABP领域服务

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

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

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 自上次更新有一个多月了,发现越往下写,越不知如何去写.特别是当遇到DDD中一些概念术语的 ...

  5. abp vNext微服务框架分析

    本文转载自:https://www.cnblogs.com/william-xu/p/11245738.html abp vNext新框架的热度一直都很高,于是最近上手将vNext的微服务Demo做了 ...

  6. [Abp vNext微服务实践] - 框架分析

    一.简介 abp vNext新框架的热度一直都很高,于是最近上手将vNext的微服务Demo做了一番研究.我的体验是,vNext的微服务架构确实比较成熟,但是十分难以上手,对于没有微服务开发经验的.n ...

  7. 实现领域驱动设计 - 使用ABP框架 - 解决方案概览

    .NET解决方案的分层 下图显示了使用ABP的 应用启动模板 创建的Visual Studio解决方案: 解决方案名称为问题跟踪,它由多个项目组成.通过考虑DDD原则以及开发和部署实践,该解决方案是分 ...

  8. 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?

    前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...

  9. ABP理论学习之领域服务

    返回总目录 本篇目录 介绍 IDomainService接口和DomainService类 样例 创建一个接口 服务实现 调用应用服务 一些讨论 何不只使用应用服务 如何强制使用领域服务 介绍 领域服 ...

随机推荐

  1. 故障重现, JAVA进程内存不够时突然挂掉模拟

    背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...

  2. Travis CI用来持续集成你的项目

    这里持续集成基于GitHub搭建的博客为项目 工具: zqz@ubuntu:~$ node --version v4.2.6 zqz@ubuntu:~$ git --version git versi ...

  3. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  4. Linq表达式、Lambda表达式你更喜欢哪个?

    什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...

  5. 记一次SQLServer的分页优化兼谈谈使用Row_Number()分页存在的问题

    最近有项目反应,在服务器CPU使用较高的时候,我们的事件查询页面非常的慢,查询几条记录竟然要4分钟甚至更长,而且在翻第二页的时候也是要这么多的时间,这肯定是不能接受的,也是让现场用SQLServerP ...

  6. Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架

    前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...

  7. 学习笔记之MVC级联及Ajax操作

    由于刚转型到MVC,MVC的架构模式很多不是很清楚,比如今天就想做个级联的操作,因为之前的ASP.NET的方式是通过:控件-->添加事件-->后台编写级联事件进行触发,但是这个MVC就不同 ...

  8. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  9. JAVA 设计模式之策略模式

    定义:定义一组算法,将每个算法都封装起来,并且使他们之间可以互换. 类型:行为类模式 策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换.在前面说过 ...

  10. 大数据之Yarn——Capacity调度器概念以及配置

    试想一下,你现在所在的公司有一个hadoop的集群.但是A项目组经常做一些定时的BI报表,B项目组则经常使用一些软件做一些临时需求.那么他们肯定会遇到同时提交任务的场景,这个时候到底如何分配资源满足这 ...