领域服务

介绍

  领域服务(或者在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. Gym 100952D&&2015 HIAST Collegiate Programming Contest D. Time to go back【杨辉三角预处理,组合数,dp】

    D. Time to go back time limit per test:1 second memory limit per test:256 megabytes input:standard i ...

  2. opencv+vs配环境

    首先,一定要注意debug和release下配的项目设置是有区分的!!!!!!!!!!! 1.注意自己的电脑是64位还是32位 2.要在环境变量中设置环境变量,环境变量从前向后扫描,用64位环境变量时 ...

  3. HDU--1213并查集

    题目传送门:HDU--1213 //题意:ignatius过生日,客人来到,他想知道他需要准备多少张桌子.然而一张桌子上面只能坐上相互熟悉的人, //其中熟悉可定义成为A与B认识,B与C认识,我们就说 ...

  4. ZipKin的原理的介绍

    结构概述       跟踪器(Tracers)存在在你的应用程序中生存,记录时间和关于操作的元数据.他们经常使用库,因此他们的使用对用户是透明的.例如,当它收到一个请求并发送一个响应时,一个感应器(i ...

  5. [国嵌攻略][104][Linux内核模块设计]

    内核模块示例 #inlcude <linux/init.h> #inlcude <linux/module.h> static int hello_init(){ printk ...

  6. Linux包管理器

    按Linux系统分类 Redhat系列:Redhat(本身就是Centos).Centos.Fedora等,采用Dpkg包管理器 Debian系列:Debian.Ubuntu等,使用RPM包管理器 R ...

  7. nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address 

    http://blog.csdn.net/ownfire/article/details/7966645 今天在做LNMP的时候,启动nginx服务,无法开启,导致网页打不开.把服务从起一下发现提示错 ...

  8. 邓_PPT

    如何拯救一份丑到爆的PPT? "小邓,这是我做的PPT,你优化优化,明天早上给我,上午客户来要用." 领导,你这是PPT嘛,明明就是word嘛. "小张啊,你看看我这个P ...

  9. Java接口和抽象类的理解

    接口和抽象类的相同之处就是 都会有抽象方法 抽象方法就是一个没有方法体 等待继承的子类完成的方法 然而接口比较严格 它的方法必须是抽象方法且是公开的 抽象类 可以有自己的属性 和 实体方法 首相用面向 ...

  10. cpuimage 开源之

    前年学习opengl做的一个小东西. 原本计划将gpuimage 的算法一个一个转写成cpu版本 c,c++ 版本. gpuimage 项目参考: https://github.com/BradLar ...