返回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领域服务的更多相关文章

  1. 线上分享-- 基于DDD的.NET开发框架-ABP介绍

    前言 为了能够帮助.Net开发者开拓视野,更好的把最新的技术应用到工作中,我在3月底受邀到如鹏网.net训练营直播间为各位学弟学妹们进行ABP框架的直播分享.同时为了让更多的.NET开发者了解ABP框 ...

  2. 基于DDD的.NET开发框架 - ABP初探

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  3. 基于DDD的.NET开发框架 - ABP分层设计

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  4. 基于DDD的.NET开发框架 - ABP仓储实现

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  5. 基于DDD的.NET开发框架 - ABP的Entity设计思想

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  6. 基于DDD的.NET开发框架 - ABP依赖注入

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  7. 基于DDD的.NET开发框架 - ABP工作单元(Unit of Work)

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  8. 基于DDD的.NET开发框架 - ABP Session实现

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

  9. 基于DDD的.NET开发框架 - ABP启动配置

    返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应 ...

随机推荐

  1. SQLServer查询锁表

    查看被锁表: select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys. ...

  2. RabbitMQ入门教程——.NET客户端使用

    众所周知RabbitMQ使用的是AMQP协议.我们知道AMQP是一种网络协议,能够支持符合要求的客户端应用和消息中间件代理之间进行通信. 其中消息代理扮演的角色就是从生产者那儿接受消息,并根据既定的路 ...

  3. 关于InnoDB的Next-Key lock

    最近一段时间在准备新员工培训的材料,本来打算介绍介绍概念就OK的,但是既然写了事务的章节,就特别想介绍一下锁,介绍了锁,就忍不住想介绍一下Next-Key Lock. 大家知道,标准的事务隔离级别有R ...

  4. mysql备份还原

    备份java代码如下: /** * 备份单个数据库 * @param dbName 数据库名称 * @return 备份成功或者失败 */ @Override public boolean backu ...

  5. 描述Linux下文件删除的原理(计时3分钟)

    Linux是通过link的数量来控制文件删除的,只有当一个文件不存在任何link的时候,这个文件才会被删除.一般来说,每个文件都有2个link计数器:i_count 和 i_nlink. i_coun ...

  6. hw 要的是螺丝钉

    日前突然接到华为HR的电话,叫我去面试。本来我的工作和工资收入等各方面在本地也还算可以,没有想要跳槽。但是本着去看看有没有更好机会的想法就去了。  9:30到了现场后,在那里等了很久,一个考官上来问了 ...

  7. 证明你是你——快速开启Windows Azure多重身份验证

    中国版Windows Azure的多重身份验证(Multi-Factor Authentication)功能已经开放.这个功能说白了就是要“证明你是你”.目前可以支持以下几种验证方式: 手机,短信验证 ...

  8. Android开发快速入门(环境配置、Android Studio安装)

    Android是一种激动人心的开源移动平台,它像手机一样无处不在,得到了Google以及其他一些开放手机联盟成员(如三星.HTC.中国移动.Verizon和AT&T等)的支持,因而不能不加以学 ...

  9. BestCoder Round #87 1003 LCIS[序列DP]

    LCIS  Accepts: 109  Submissions: 775  Time Limit: 4000/2000 MS (Java/Others)  Memory Limit: 65536/65 ...

  10. CF 672C Recycling Bottles[最优次优 贪心]

    C. Recycling Bottles time limit per test 2 seconds memory limit per test 256 megabytes input standar ...