返回ABP系列html
ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。git
ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。github
ABP的官方网站:http://www.aspnetboilerplate.comweb
ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents框架
Github上的开源项目:https://github.com/aspnetboilerplate网站
1、概念介绍this
领域服务(或DDD中的服务)用于执行领域操做和业务规则。Eric Evans描述了一个好的服务应该具有下面三个特征:spa
一、和领域概念相关的操做不是一个实体或者值对象的本质部分。设计
二、接口定义在领域模型其余元素的条款中。code
三、操做是无状态的。
跟得到或返回一个数据传输对象的应用服务方法(DTO)不一样,领域服务得到或者返回一个领域对象(好比实体或值类型)。
一个领域服务能够用于应用服务,也能够用于其余的领域服务,但不能直接用于展示层,服务层才直接用于展示层。
2、IDomainService接口和DomainService类
ABP定义了IDomainService接口,全部的领域服务都按照惯例实现了该接口。当实现时,领域服务会以transient自动注册到依赖注入系统。
此外,领域服务(可选地)能够从DomainService类继承。所以,它可使用一些继承的属性,好比logging,本地化等等。固然,若是没有继承,若是须要的话也能够注入这些属性。
3、示例
假设咱们有一个任务管理系统而且有将一个任务派给一我的的业务规则。
一、建立一个接口
首先咱们为该服务定义一个接口(不是必须的,可是这样是一个好的实践):
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(领域服务)。
4、讨论
基于上面的例子,你可能会存在下面的疑问。
一、为什么不仅使用应用服务
咱们能够简单地说,它不是应用服务要干的活。由于领域逻辑不是一个用例(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方法,并且不能跳过业务规则了。