领域服务

介绍

  领域服务(或者在DDD中单纯的服务)用来执行领域操作和业务规则。Eric Evans在他的DDD书中描述了一个好的服务有三个特征:

  1. 与领域概念关联的操作,但不是实体或值对象的自然组成部分。

  2. 接口的定义依照领域模型的其他元素。

  3. 操作是无状态的。

  不像应用服务那样获取或返回DTO,领域服务获取或返回领域对象(如实体或值对象)。

  领域服务可以被应用服务或其他领域服务使用,但不能被展现层(应用层可以)直接使用。

IDomainService接口和DomainService类

  ABP定义了IDomainService接口,并约定所有的领域服务都实现这个接口。当被实现时,领域服务自动注册到依赖注入系统,调用类型为临时的。

  领域服务可以继承DomainService类。从而,可以使用一些继承属性来记录日志,本地化等等。当然,即使没有继承,如果需要的话也可以注入他们。

示例

  假定我们有一个任务管理系统,当分配任务到一个人的时候需要执行业务规则。

创建接口

  首先,我们定义这个服务的接口(不是必须,但最好这样作为最佳实践):

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

  如上所见,TaskManager服务使用领域对象工作:TaskPerson。命名领域服务有些约定,可以为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;
}
}

  这里,我们有两条业务规则:

  • 一个任务的状态为Active,才可以分配给一个新的人。
  • 一个人最多有3个Active状态的任务。

  你可能会想为什么第一次检查时抛出ApplicationException而第二次检查时抛出UserFriendlyException。这个和领域服务没有任何关系。在这里仅仅是个例子,抛出哪种类型异常完全由我们自己决定。我认为,用户接口必须检查任务状态,并且不允许我们分配任务到人,这是一个应用错误,应该对用户不可见。第二个对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);
}
}

  任务应用服务使用指定的DTO(input)和仓储获取相关的任务和人,并把他们传递给Task Manager(领域服务)。

一些探讨

  基于上面的示例,你可能有些问题。

为什么只有应用服务?

  你可能会说为什么应用服务不实现在领域服务的逻辑?

  我们可以简单来说,这不是应用服务的任务。因为这不是一个用例而是业务操作。我们或许会使用同样的“分配一个任务到人”的领域逻辑在不同的用例下。比方说,我们可能有另一个场景,某种情况下更新了这个任务并且这个更新包含分配这个任务到另一个人。所以,我们可以在这里使用相同的领域逻辑。还有,我们可能有两个不同的UI(一个移动应用和一个web应用)共享相同的领域或者我们可能有有一个为远程客户端使用的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的setter为protected。所以,在Task实体类之外将不能更改它。添加一个AssignToPerson方法,接收一个person和task plicy。CheckIfCanAssignTaskToPerson方法检查是否为有效的分配,如果无效则抛出一个合适的异常(它的实现在这里不重要)。然后应用服务方法将变为如下所示:

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

  我们注入了ITaskPolicy作为_taskPolicy并把他传递给AssignToPersion方法。现在,没有第二种方式分配一个任务到人了。我们应该总是使用AssignToPerson方法,并且不能跳过业务规则。

返回主目录

ABP官方文档翻译 3.4 领域服务的更多相关文章

  1. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  2. ABP官方文档翻译 3.6 工作单元

    工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...

  3. ABP官方文档翻译 3.5 规约

    规约 介绍 示例 创建规范类 使用仓储规约 组合规约 讨论 什么时候使用? 什么时候不使用? 介绍 规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则.(维基百科). ...

  4. ABP官方文档翻译 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  5. ABP官方文档翻译 2.1 依赖注入

    依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager ...

  6. ABP官方文档翻译 1.2 N层架构

    N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构, ...

  7. ABP官方文档翻译 2.6 定时

    定时 介绍 时钟 客户端 时区 客户端 Binders和Converters 介绍 一些应用只针对一个时区,而其他的一些已用则有许多不同的时区.为了满足这样的需求和集中的时间操作,Abp提供了时间操作 ...

  8. ABP官方文档翻译 4.1 应用服务

    应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...

  9. ABP官方文档翻译 3.3 仓储

     仓储 默认仓储 自定义仓储 自定义仓储接口 自定义仓储实现 基础仓储方法管理数据库连接 查询 获取单个实体 获取实体列表 关于IQueryable 自定义返回值 插入 更新 删除 其他 关于异步方法 ...

随机推荐

  1. Vijos P1786 质因数分解【暴力】

    质因数分解 背景 NOIP2012普及组第一题 描述 已知正整数n是两个不同的质数的乘积试求出较大的那个质数. 格式 输入格式 输入只有一行包含一个正整数n. 输出格式 输出只有一行包含一个正整数p, ...

  2. linux命令 uname -r 和 uname -a 的解释与演示

    1.uname -r : 显示操作系统的发行版号2.uname -a :显示系统名.节点名称.操作系统的发行版号.内核版本等等. 系统名:Linux 节点名称: iZ2zeeailqvwws5dcui ...

  3. EMC题

    [面试题]EMC易安信面试题解 1. 除以59的余数是多少. 来自wiki:费马小定理是数论中的一个定理:假如a是一个整数,p是一个質数,那么 如果a不是p的倍数,这个定理也可以写成 这个书写方式更加 ...

  4. solr服务的搭建(以solr4.1实现)

    1.准备工作:一个干净的Tomcat,solr-4.10.3. 2.新建一个文件夹我这里命名为solr,将Tomcat和solr-4.10.3放进去.新建一个solrhome的文件夹,里面放的是sol ...

  5. pycharm中一直跳出updating indices...indexing

    直接比较明显的就是cpu直冲天际. pycharm是一款用了就不愿意换的ide,因为他的功能十分强大,同时也有着让人诟病的问题,就是他功能太全了,以至于有的功能你这辈子可能都不会去触碰,带来的直接问题 ...

  6. for语句,你真正搞懂了吗?

    今天看书时,无意间看到了这个知识点,啥知识点?也许在各位大神看来,那是再简单不过的东西了. 说来惭愧.原来直到今天我才真正搞懂for语句. for语句的结构如下所示: for(语句A;语句B;语句C) ...

  7. 使用django建博客时遇到的URLcon相关错误以及解决方法。错误提示:类型错误:include0获得一个意外的关键参数app_name

    root@nanlyvm:/home/mydj/mysite# python manage.py runserver Performing system checks... Unhandled exc ...

  8. vi的常用命令

    https://zhidao.baidu.com/question/332242228.html vi的基本操作 a) 进入vi 在系统提示符号输入vi及文件名称后,就进入vi全屏幕编辑画面: $ v ...

  9. Git学习(1)-本地版本库的创建

    我用的是Git-2.14.3-64-bit版本,在windows64位上运行的,把软件分享下链接:http://pan.baidu.com/s/1jIoZ7Xc 密码:13q2. 安装及配置自行百度, ...

  10. windows安装xampp时出现,unable to realloc xxxxxxxx bytes

    摘录自:http://blog.csdn.net/lz610756247/article/details/70842166 Windows虚拟内存的设置 问题描述:由于开启虚拟内存会导致硬盘IO性能下 ...