基于DDD的.NET开发框架 - ABP领域服务
ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。
ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板。
ABP的官方网站:http://www.aspnetboilerplate.com
ABP官方文档:http://www.aspnetboilerplate.com/Pages/Documents
Github上的开源项目:https://github.com/aspnetboilerplate
一、概念介绍
领域服务(或DDD中的服务)用于执行领域操作和业务规则。Eric Evans描述了一个好的服务应该具备下面三个特征:
1、和领域概念相关的操作不是一个实体或者值对象的本质部分。
2、接口定义在领域模型其他元素的条款中。
3、操作是无状态的。
跟获得或返回一个数据传输对象的应用服务方法(DTO)不同,领域服务获得或者返回一个领域对象(比如实体或值类型)。
一个领域服务可以用于应用服务,也可以用于其他的领域服务,但不能直接用于展现层,服务层才直接用于展现层。
二、IDomainService接口和DomainService类
ABP定义了IDomainService接口,所有的领域服务都按照惯例实现了该接口。当实现时,领域服务会以transient自动注册到依赖注入系统。
此外,领域服务(可选地)可以从DomainService类继承。因此,它可以使用一些继承的属性,比如logging,本地化等等。当然,如果没有继承,如果需要的话也可以注入这些属性。
三、示例
假设我们有一个任务管理系统并且有将一个任务派给一个人的业务规则。
1、创建一个接口
首先我们为该服务定义一个接口(不是必须的,但是这样是一个好的实践):
public interface ITaskManager : IDomainService
{
void AssignTaskToPerson(Task task, Person person);
}
可以看到,TaskManager服务使用领域对象工作:一个Task 和一个Person。命名领域服务时存在一些惯例。它可以是TaskManager,TaskService或者TaskDomainService...
2、服务实现
先来看看下面这个实现:
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个激活的任务。
你可能想知道为啥第一次检测时抛出了一个ApplicationException,而第二次检查时抛出了UserFriendlyException,请关注后面博客的异常处理。这根领域服务根本无关。这里这样处理的想法是这样的,UI必须先要检查一个任务的状态,否则不应该允许我们将它派给一个人。这是一个应用程序的错误,并且我们可以向用户隐藏这个错误。对于第二个友好的异常信息,UI检查更加困难,而且我们可以向用户显示一个可读的错误信息。这只是一个例子而已。
3、调用应用服务
现在,来看看如何在一个应用服务中使用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(领域服务)。
四、讨论
基于上面的例子,你可能会存在下面的疑问。
1、为何不只使用应用服务
我们可以简单地说,它不是应用服务要干的活。因为领域逻辑不是一个用例(use-case),而是一个 业务操作。我们可以在不同的用例中使用相同的“将一个任务派给一个人”的逻辑。比如说我们以后会更新这个任务,并且将这个任务派给其他人。因此,我们可以使用相同的领域逻辑,这个逻辑就是“将一个任务派给一个人”,我们不用考虑这个具体的人和具体的任务。此外,我们可能有两个不同的UI(一个移动端应用和一个web应用)来共享相同的领域。
应用服务层中有一个应用服务方法,但却使用到了领域层的三个业务逻辑,因为在领域层中,获取单个task和person都各自为一个业务逻辑,将一个任务派给一个人又是一个业务逻辑。在应用服务层,我们只需要获得一个人和一个任务就行,然后将该任务派给这个人,根本不需要考虑这个人和这个任务的获取细节,也不用考虑任务派发的细节,因为这完全不是应用层考虑的事儿。
如果你的领域很简单,只有一个UI并且将一个任务派发给一个人在单点处就可以完成,那么你可以跳过领域服务,然后在应用服务层实现该逻辑。虽然这不是DDD的最佳实践,但是ABP不会强制你这么设计。
2、如何强制使用领域服务
你可以看到,应用服务只能做下面的事情:
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方法,而且不能跳过业务规则了。
基于DDD的.NET开发框架 - ABP领域服务的更多相关文章
- 线上分享-- 基于DDD的.NET开发框架-ABP介绍
前言 为了能够帮助.Net开发者开拓视野,更好的把最新的技术应用到工作中,我在3月底受邀到如鹏网.net训练营直播间为各位学弟学妹们进行ABP框架的直播分享.同时为了让更多的.NET开发者了解ABP框 ...
- 基于DDD的.NET开发框架 - ABP初探
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP分层设计
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP仓储实现
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP的Entity设计思想
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP依赖注入
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP工作单元(Unit of Work)
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP Session实现
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
- 基于DDD的.NET开发框架 - ABP启动配置
返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...
随机推荐
- Oracle global database name与db link的纠缠关系
ORACLE数据库中Global Database Name与DB LINKS的关系还真是有点纠缠不清,在说清楚这个关系前,我们先来了解一下Global Database Name的概念 Global ...
- maven-shade-plugin
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> ...
- SQL Server:孤立用户详解
SQL Server 的用户安全管理分两层,整个SQL Server 服务器一层,每个数据库一层. 在服务器层的帐号,叫登录账户(SQL Server:服务器角色),可以设置它管理整个SQL Serv ...
- 用struts和hibernate结合MVC层实例
1.倒包hibernate11个包+sturts2 13个包 2.创建web.xml <?xml version="1.0" encoding="UTF-8&quo ...
- 关于/etc/rc.local以及/etc/init.d
1. /etc/rc.local 这是使用者自订开机启动程序,把需要开机自动运行的程序写在这个脚本里 --------引用---------------------- 在完成 run le ...
- linux系统的7种运行级别
Linux系统有7个运行级别(runlevel)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆运行级别2:多 ...
- android 新建项目中去掉标题栏
1.新建new android application project theme选none 并打钩创建一个Blank Activity 运行如下图所示: 2.若想把标题栏去掉,更改Manifestr ...
- nodejs模块——http模块
http模块主要用于创建http server服务. 本文还用到url模块和path模块,还有fs模块.url模块用于解析url,path模块用于处理和转换文件路径. 一.简单应用 代码如下: // ...
- Solrj和Solr DIH索引效率对比分析
测试软件环境: 1.16G windows7 x64 32core cpu . 2.jdk 1.7 tomcat 6.x solr 4.8 数据库软件环境: 1.16G windows7 x64 ...
- POJ 1584 A Round Peg in a Ground Hole --判定点在形内形外形上
题意: 给一个圆和一个多边形,多边形点可能按顺时针给出,也可能按逆时针给出,先判断多边形是否为凸包,再判断圆是否在凸包内. 解法: 先判是否为凸包,沿着i=0~n,先得出初始方向dir,dir=1为逆 ...