返回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. Solr术语介绍:SolrCloud,单机Solr,Collection,Shard,Replica,Core之间的关系

    Solr有一堆让人发晕的术语如:collections,shards,replicas,cores,config sets. 在了解这些术语之前需要先做做如下功课: 1)什么是倒排索引? 2)搜索引擎 ...

  2. SQL中JOIN 的用法

    关于sql语句中的连接(join)关键字,是较为常用而又不太容易理解的关键字,下面这个例子给出了一个简单的解释 --建表table1,table2:create table table1(id int ...

  3. 模块module

    python中的Module相当于C++中头文件和命名空间的组合体,便于代码的组织,任何一个python代码的文件都是一个Module,都可以被其他模块import import,from...imp ...

  4. 必须知道的八大种排序算法【java实现】(二) 选择排序,插入排序,希尔算法【详解】

    一.选择排序 1.基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换:然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止. 2.实例 3.算法 ...

  5. HDFS的Trash回收站功能

    文件的删除和恢复 和Linux系统的回收站设计一样,HDFS会为每一个用户创建一个回收站目录:/user/用户名/.Trash/,每一个被用户通过Shell删除的文件/目录,在系统回收站中都一个周期, ...

  6. CSS background-position 用法详细介绍

    语法: background-position : length || length background-position : position || position 取值: length  : ...

  7. LNMP环境搭建

    LNMP环境搭建 Linux + Nginx + MySQL + PHP PHP是一种脚本语言,当前中国乃至世界上使用PHP语言开发的网站非常普遍 Nginx是一个web服务软件,和apache是一类 ...

  8. bg激活后台运行的服务

    按redis重启来做案例 ./redis-server Ctrl+z让执行的命令在后台暂停 [1]+ Stopped ./redis-server 这个时候他是把这个服务放到后台了,可是ctrl+z是 ...

  9. 理解 OpenStack + Ceph (5):OpenStack 与 Ceph 之间的集成 [OpenStack Integration with Ceph]

    理解 OpenStack + Ceph 系列文章: (1)安装和部署 (2)Ceph RBD 接口和工具 (3)Ceph 物理和逻辑结构 (4)Ceph 的基础数据结构 (5)Ceph 与 OpenS ...

  10. MMORPG大型游戏设计与开发(客户端架构 part2 of vegine)

    一个好的接口是尽可能让更多实用的方法进行整理封装,要记住的是不常用的方法和类最好不好封装到接口中,因为那样会造成本身的困惑.基础模块中并没有太多封装,甚至连一个类的封装也没有,而是一些很常用的工具方法 ...