ABP理论学习之领域服务
本篇目录
介绍###
领域服务(或DDD中的服务)用于执行领域操作和业务规则。Eric Evans描述了一个好的服务应该具备下面三个特征:
- 和领域概念相关的操作不是一个实体或者值对象的本质部分。
- 接口定义在领域模型其他元素的条款中。
- 操作是无状态的。
跟获得或返回一个数据传输对象的应用服务方法(DTO)不同,领域服务获得或者返回一个领域对象(比如实体或值类型)。
一个领域服务可以用于应用服务,也可以用于其他的领域服务,但不能直接用于展现层,服务层才直接用于展现层。
IDomainService接口和DomainService类###
ABP定义了IDomainService接口,所有的领域服务都按照惯例实现了该接口。当实现时,领域服务会以transient自动注册到依赖注入系统。
此外,领域服务(可选地)可以从DomainService类继承。因此,它可以使用一些继承的属性,比如logging,本地化等等。当然,如果没有继承,如果需要的话也可以注入这些属性。
样例###
假设我们有一个任务管理系统并且有将一个任务派给一个人的业务规则。
创建一个接口
首先我们为该服务定义一个接口(不是必须的,但是这样是一个好的实践):
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;
}
}
上面的代码定义了两个业务规则:
- 一个任务为了能够派给一个新人,它应该是Active(激活)的状态
- 一个人可以最多可以有3个激活的任务。
你可能想知道为啥第一次检测时抛出了一个ApplicationException,而第二次检查时抛出了UserFriendlyException,请关注后面博客的异常处理。这根领域服务根本无关。这里这样处理的想法是这样的,UI必须先要检查一个任务的状态,否则不应该允许我们将它派给一个人。这是一个应用程序的错误,并且我们可以向用户隐藏这个错误。对于第二个友好的异常信息,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);
}
}
Task应用服务使用给定的DTO(输入)和仓储来检索相关的task和 person,并将它们传给 TaskManager(领域服务)。
一些讨论###
基于上面的例子,你可能会存在下面的疑问。
何不只使用应用服务
你可能会问,为什么不使用应用服务实现领域服务中的逻辑呢?
我们可以简单地说,它不是应用服务要干的活。因为领域逻辑不是一个用例(use-case),而是一个 业务操作。我们可以在不同的用例中使用相同的“将一个任务派给一个人”的逻辑。比如说我们以后会更新这个任务,并且将这个任务派给其他人。因此,我们可以使用相同的领域逻辑,这个逻辑就是“将一个任务派给一个人”,我们不用考虑这个具体的人和具体的任务。此外,我们可能有两个不同的UI(一个移动端应用和一个web应用)来共享相同的领域。
下面根据个人的理解来画个图:
如上图,应用服务层中有一个应用服务方法,但却使用到了领域层的三个业务逻辑,因为在领域层中,获取单个task和person都各自为一个业务逻辑,将一个任务派给一个人又是一个业务逻辑。在应用服务层,我们只需要获得一个人和一个任务就行,然后将该任务派给这个人,根本不需要考虑这个人和这个任务的获取细节,也不用考虑任务派发的细节,因为这完全不是应用层考虑的事儿。
如果你的领域很简单,只有一个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; }
//...其他成员
public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
{
taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
AssignedPersonId = person.Id;
}
}
可以将AssignedPersonId的setter改成protected。这样,它就不能在Task实体类之外改变了。添加一个需要一个Person和ITaskPolicy的参数。CheckIfCanAssignTaskToPerson方法检查这是否是一个有效的派发,如果无效就抛出一个适当的异常。最后,应用服务方法应该是这个样子的:
public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
var task = _taskRepository.Get(input.TaskId);
var person = _personRepository.Get(input.PersonId);
task.AssignToPerson(person, _taskPolicy);
}
现在,不存在将一个任务派给一个人的第二种方法了。我们应该总是要使用AssignToPerson方法,而且不能跳过业务规则了。
ABP理论学习之领域服务的更多相关文章
- 基于ABP实现DDD--领域服务、应用服务和DTO实践
什么是领域服务呢?领域服务就是领域对象本身的服务,通常是通过多个聚合以实现单个聚合无法处理的逻辑. 一.领域服务实践 接下来将聚合根Issue中的AssignToAsync()方法[将问题分配给用 ...
- ABP框架 - 领域服务
文档目录 本节内容: 简介 例子 创建一个接口 实现服务 使用应用服务 相关论述 为什么不只用应用服务? 如何强制你使用领域服务? 简介 领域服务(或服务)用来执行领域操作和业务规则.Eric Eva ...
- 基于DDD的.NET开发框架 - ABP领域服务
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 关于ABP——领域服务的思考
我在刚接触ABP的时候一直有一个疑问--有了应用服务,为什么还需要领域服务呢? 领域服务和应用服务对比 领域服务 应用服务 返回值 Entity DTO 被表现层调用 不可以(非强制) 可以 在ABP ...
- ABP入门系列(18)—— 使用领域服务
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 自上次更新有一个多月了,发现越往下写,越不知如何去写.特别是当遇到DDD中一些概念术语的 ...
- DDD理论学习系列(8)-- 应用服务&领域服务
DDD理论学习系列--案例及目录 1. 引言 单从字面理解,不管是领域服务还是应用服务,都是服务.而什么是服务?从SOA到微服务,它们所描述的服务都是一个宽泛的概念,我们可以理解为服务是行为的抽象.从 ...
- ABP官方文档翻译 3.4 领域服务
领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在D ...
- 初识ABP vNext(11):聚合根、仓储、领域服务、应用服务、Blob存储
Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 聚合根 仓储 领域服务 BLOB存储 应用服务 单元测试 模块引用 最后 前言 在前两节中介绍了ABP模块开发的基本步 ...
- 基于ABP落地领域驱动设计-04.领域服务和应用服务的最佳实践和原则
目录 系列文章 领域服务 应用服务 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践 ...
随机推荐
- Android MarginEnd与MarginStart (RTL)
Android MarginLeft与MarginStart的区别http://blog.csdn.net/zhufuing/article/details/40181815 在写layout布局的时 ...
- 使用dd命令备份Linux分区
为了备份分区,开始使用的是Remastersys,但最终生成的iso文件仅有几十K,应该是软件bug,且此软件不再更新,后尝试使用Linux Respin,但github一直连接不上. 其实可以尝试使 ...
- DNG格式解析
Author:Maddock Date:2015.04.22 转载请注明出处:http://www.cnblogs.com/adong7639/p/4446828.html DNG格式基本概念 DNG ...
- 获取 windows 商店内的 aapx 安装包 并 安装(旁加载)
这是一篇教程,写在 win10 版<量子破碎>发售近期. 主要原因:windows 商城的应用下载实在难以忍受...... #######2016-4-9更新####### 4-6号通过旁 ...
- ABP集合贴(转)
ABP集合贴 本文背景 公司最近规划的新框架准备基于ABP来搭建,自从在阳铭博客看到ABP框架的介绍后,就一直持续关注着,但还没真正在实际项目中直接使用ABP,只是自己做了一些学习和Demo.ABP所 ...
- H5 video的使用
html5 video使用记录: 1.<video>的基本属性: preload: (预加载)iPhone支持,Android不一定支持; poster: (封面图片)iPhone支持 ...
- xsl-mode属性用法
本文出自 “熔 岩” 博客,出处http://lavasoft.blog.51cto.com/62575/60517 对某一个元素做多次处理,那么选择<xsl:apply-template元素处 ...
- 用Fiddler的自动响应模拟系统集成
1. 下载最新版本的Fiddler Fiddler 官网 2, 安装并启动Fiddler 3, 勾选自动响应 见上图 4, 添加自动响应规则 见上图 5, 添加自动响应内容文件 添加响应文件到Fidd ...
- <转>Unity3D研究院之C#使用Socket与HTTP连接服务器传输数据包
最近项目中需要使用HTTP与Socket,把自己这段时间学习的资料整理一下.有关Socket与HTTP的基础知识MOMO就不赘述拉,不懂得朋友自己谷歌吧.我们项目的需求是在登录的时候使用HTTP请求, ...
- SqlServer windowss身份登陆和sa身份登陆
今天重新装了系统,但是计算机名变了,于是修改了计算机名,然后装了SQLSEVER,安装完成后登录,发现无论用WINDOWS身份还是SQLSERVER身份都登录不了 1.先说说sqlserver身份登录 ...