领域服务

介绍

  领域服务(或者在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. [bzoj1717][Usaco2006 Dec]Milk Patterns 产奶的模式 (hash构造后缀数组,二分答案)

    以后似乎终于不用去学后缀数组的倍增搞法||DC3等blablaSXBK的方法了= = 定义(来自关于后缀数组的那篇国家集训队论文..) 后缀数组:后缀数组SA是一个一维数组,它保存1..n的某个排列S ...

  2. javaScript原生定义的函数

    1.JavaScript中的算术运算 包括加(+).减(-).乘(*).除(/)和求余(取模)(%)运算,除了这些基本的运算外,JavaScript还支持更加复杂的算术运算,这些复杂算术运算作为Mat ...

  3. [国嵌笔记][026][ARM伪指令]

    ARM机器码 1.汇编程序通过汇编器变成机器码,然后才能在ARM处理器上运行 2.ARM机器码是一个32位的数,被分成了多个段,每个段都有各自的含义 3.格式: cond:表示条件(4位) I:表示源 ...

  4. javascript alert乱码的解决方法

    http://www.jb51.net/article/42805.htm 提示时中文乱码,拼音什么的都没有问题呀,下面我在论坛找到一解决办法,下面与大家分享. 解决办法一: 复制代码 代码如下: e ...

  5. 邓_ecshop

    =========================================== 版本错误: error_reporting(0); ============================== ...

  6. ios - 如何获取app上的数据

    做过ios开发的人应该都用过Charles,通常叫它花瓶.Charles是Mac下常用的对网络流量进行分析的工具,类似于Windows下的Fiddler.在开发iOS程序的时候,往往需要调试客户端和服 ...

  7. ASP.NET Core下发布网站

    一.windows下发布到IIS 1.前奏:IIS上的准备 (1)IIS 必须安装AspNetCoreModule 模块 下载地址:(DotNetCore.2.0.3-WindowsHosting-a ...

  8. iOS的相对路径和绝对路径

    iOS程序有固定的文件访问限制,只能在自己的沙盒内. UIImage *img=[UIImage imageNamed:@"cellicon.png"]; 这段代码从相对路径加载了 ...

  9. 导入Mybatis_Spring项目遇到的问题

    1.  问题: jdk版本不匹配  解决方法:首先 到项目空间的   .setting文件中找到  org.eclipse.wst.common.project.facet.core.xml  修改参 ...

  10. jenkins插件之如何优雅的生成版本号

    一.简介 在持续集成中,版本管理是非常重要的一部分,本章将介绍如何Version Number Plug插件生成优雅的版本号. 二.安装 系统管理-->插件管理 搜索 Version Numbe ...