Introduction

Domain Services (or just Service, in DDD) is used to perform domain operations and business rules. Eric Evans describes a good Service in three characteristics (in his DDD book):

  1. The operation relates to a domain concept that is not a natural part of an Entity or Value Object.
  2. The interface is defined in terms of other elements of domain model.
  3. The operation is stateless.
  4. 域服务(或DDD中的服务)用于执行域操作和业务规则。Eric Evans描述了一个好的服务在三个特点(在他的DDD书):

    该操作涉及一个域概念,该概念不是实体或值对象的自然部分。
    接口是根据域模型的其他元素定义的。
    该操作是无状态的。

Unlike Application Services which gets/returns Data Transfer Objects, a Domain Service gets/returns domain objects (like entities or value types).

A Domain Service can be used by Application Services and other Domain Services, but not directly used by presentation layer (application service is for that).

IDomainService Interface and DomainService Class

ASP.NET Boilerplate defines IDomainService interface that is implemented by all domain services conventionally. When it's implemented, the domain service is automatically registered to Dependency Injection system astransient.

Also, a domain service (optionally) can inherit from DomainService class. Thus, it can use power of some inherited properties for logging, localization and so on... Surely, even if it does not inherit, it can inject they if needs.

另外,域名服务(可选)可以从域服务类继承。因此,它可以使用某些继承属性的权限进行日志记录、本地化等操作…当然,即使它不继承,它也可以在需要时注入它们。

Example

Assume that we have a task management system and we have business rules while assigning a task to a person.

Creating an Interface

First, we define an interface for the service (not required, but as a good practice):

public interface ITaskManager : IDomainService
{
void AssignTaskToPerson(Task task, Person person);
}

As you can see, TaskManager service works with domain objects: a Task and a Person. There are some conventions of naming domain services. It can be TaskManager, TaskService or TaskDomainService...

Service Implementation

Let's see the implementation:

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;
}
}

We have two business rules here:

  • A task should be in Active state in order to assign it to a new Person.
  • A person can have a maximum of 3 active tasks.
  • 任务应处于活动状态,以便将其分配给新的人员。
    一个人最多可以有3个活动任务。

You can wonder why I throwed an ApplicationException for first check and UserFriendlyException for second check (see exception handling). This is not related to domain services at all. I did this for just an example, it completely up to you. I thought that user interface must check a task's state and should not allow us to assign it to a person. I think this is an application error and we may hide it from user. Second one is harder to check by UI and we can show a readable error message to the user. Just for an example.

You can wonder why I throwed an ApplicationException for first check and UserFriendlyException for second check (see exception handling).这与域服务无关。我这样做只是为了一个例子,这完全取决于你。我认为用户界面必须检查一个任务的状态,不应该允许我们把它分配给一个人。我认为这是一个应用程序错误,我们可以从用户那里隐藏它。第二个更难通过UI检查,我们可以向用户显示可读的错误消息。就举个例子吧。

Using from Application Service

Now, let's see how to use TaskManager from an application service:

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 Application Service uses given DTO (input) and repositories to retrieve related task and person and passes them to the Task Manager (the domain service).

Some Discussions(一些讨论)

Based on the example above, you may have some questions.

Why Not Only Application Services?

You can say that why application service does not implement the logic in the domain service?

We can simply say that it's not application service task. Because it's not a use-case, instead, it's a business operation. We may use same 'assign a task to a user' domain logic in a different use-case. Say that we may haveanother screen to somehow update the task and this updating can include assigning the task to another person. So, we can use same domain logic there. Also, we may have 2 different UI (one mobile application and one web application) that shares same domain or we may have a web API for remote clients that includes a task assign operation.

我们可以简单地说,它不是应用程序服务任务。因为它不是一个用例,而是一个业务操作。我们可以在不同的用例中使用相同的“将任务分配给用户域逻辑”。说我们可以一个屏幕来更新任务,这种更新包括对他人分配任务。因此,我们可以使用相同的域逻辑。另外,我们可能有2个不同的UI(一个移动应用程序和一个Web应用程序)共享同一个域,或者我们可能有一个远程客户机的Web API,其中包含一个任务分配操作。

If your domain is simple, will have only one UI and assigning a task to a person will be done in just a single point, then you may consider to skip domain services and implement the logic in your application service. This will not be a best practice for DDD, but ASP.NET Boilerplate does not force you for such a design.

如果您的域很简单,只会有一个UI,并且分配给一个人的任务将只在一个点上完成,那么您可以考虑跳过域服务并在应用程序服务中实现逻辑。这会不会是国内最好的实践,但ASP.NET样板不强迫你这样的设计。

How to Force to Use the Domain Service?

You can see that, application service simply could do that:

public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
var task = _taskRepository.Get(input.TaskId);
task.AssignedPersonId = input.PersonId;
}

The developer write the application service may not know there is a TaskManager and can directly set given PersonId to task's AssignedPersonId. So, how to prevent it? There are many discussions in DDD area based on these and there are some used patterns. We will not go very deep. But, we will provide a simple way of doing it.

开发者编写应用程序的服务可能不知道有一个工具可以直接设定任务的assignedpersonid PersonId。那么,如何预防呢?基于此,DDD领域有很多讨论,也有一些使用过的模式。我们不会走得很深。但是,我们将提供一个简单的方法来做它。

We can change Task entity as shown below:

public class Task : Entity<long>
{
public virtual int? AssignedPersonId { get; protected set; } //...other members and codes of Task entity public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
{
taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
AssignedPersonId = person.Id;
}
}

We changed setter of AssignedPersonId as protected. So, it can not be changed out of this Task entity class. Added an AssignToPerson method that takes a person and a task policy. CheckIfCanAssignTaskToPersonmethod checks if it's a valid assignment and throws a proper exception if not (it's implementation is not important here). Then application service method will be like that:

public void AssignTaskToPerson(AssignTaskToPersonInput input)
{
var task = _taskRepository.Get(input.TaskId);
var person = _personRepository.Get(input.PersonId); task.AssignToPerson(person, _taskPolicy);
}

We injected ITaskPolicy as _taskPolicy and passed to AssignToPerson method. Now, there is no second way of assigning a task to a person. We should always use AssignToPerson and can not skip business rules.

我们将ITaskPolicy作为_taskpolicy并通过assigntoperson方法。现在,没有第二种方法将任务分配给一个人。我们应该总是使用assigntoperson不能跳过业务规则。

ABP框架系列之二十一:(Domain-Services-领域服务)的更多相关文章

  1. ABP框架系列之二十:(Dependency-Injection-依赖注入)

    What is Dependency Injection If you already know Dependency Injection concept, Constructor and Prope ...

  2. ABP框架系列之二十四:(Email-Sending-EF-电子邮件发送)

    Introduction Email sending is a pretty common task for almost every application. ASP.NET Boilerplate ...

  3. ABP框架系列之二:(Entity Framework Core-实体核心框架)

    Introduction(介绍) Abp.EntityFrameworkCore nuget package is used to integrate to Entity Framework (EF) ...

  4. ABP框架系列之二十七:(Feature-Management-特征管理)

    Introduction Most SaaS (multi-tenant) applications have editions (packages) those have different fea ...

  5. ABP框架系列之二十六:(EventBus-Domain-Events-领域事件)

    In C#, a class can define own events and other classes can register it to be notified when something ...

  6. ABP框架系列之二十二:(Dynamic-Web-API-动态WebApi)

    Building Dynamic Web API Controllers This document is for ASP.NET Web API. If you're interested in A ...

  7. ABP框架系列之二十九:(Hangfire-Integration-延迟集成)

    Introduction Hangfire is a compherensive background job manager. You can integrate ASP.NET Boilerpla ...

  8. ABP框架系列之二十八:(Handling-Exceptions-异常处理)

    Introduction This document is for ASP.NET MVC and Web API. If you're interested in ASP.NET Core, see ...

  9. ABP框架系列之二十五:(Embedded-Resource-Files-嵌入式资源文件)

    Introduction ASP.NET Boilerplate provides an easy way of using embedded Razor views (.cshtml files) ...

随机推荐

  1. Lepus监控之SQLServer配置(后续整理)

    1.安装pymssql模块 1-1.环境准备: 1-1-1.unixODBC安装 yum install unixODBC unixODBC-devel -y 1-1-2.freetds安装下载 fr ...

  2. PyQt5系列教程(六)如何让界面和逻辑分离

    软硬件环境 OS X EI Capitan Python 3.5.1 PyQt 5.5.1 PyCharm 5.0.3 前言 前面的内容我们介绍了利用QtDesigner来设计界面,再通过命令行工具p ...

  3. 插件: Hammer.js

    官网: http://hammerjs.github.io/  hammer.js 官网 http://hammerjs.github.io/api/ 官网API(官网写的实在太简了!不好用.注意里面 ...

  4. dubbo配置方式简单介绍

    原地址:http://www.cnblogs.com/chanshuyi/p/deep_insight_dubbo_config.html 一.介绍 Dubbo 采用全Spring配置方式,透明化接入 ...

  5. sql server 查看锁表SQL【转】

    1.select * from sys.dm_tran_locks或sp_LOCK 查看request_node 字段中为'X'(排他锁)或'IX'(意向排他锁)2.用sp_who2 + pid(进程 ...

  6. ArcGIS自定义工具箱-字段值部分替换

    ArcGIS自定义工具箱-字段值部分替换 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 目的:替换某个字段中的字符串 用例:湖南省长沙市=>湖南/长沙:临湘县 ...

  7. 吴裕雄 python深度学习与实践(12)

    import tensorflow as tf q = tf.FIFOQueue(,"float32") counter = tf.Variable(0.0) add_op = t ...

  8. Tomcat-servlet基础

    1.1 概念 运行在服务器上的小程序   定义了浏览器访问到(tomact)的规则 1.2 步骤 1.3 执行原理 1  当服务器 接收到客户端浏览器的请求后  会解析url地址  获得url路径   ...

  9. 加入EOS主网

    [加入EOS主网] 根据之前的博文,可以直接在本地测试单节点网络.这里再给出一下.详情见[参考1]. alias cleos='docker exec -it eosio /opt/eosio/bin ...

  10. 安装64位office时,弹出提示,要求卸载32位office

    运行 regedit,进入到HKEY_CLASSES_ROOT\Installer\Products下,删除0000510开头的项或00002开头项.然后重启计算机. 参考:  https://blo ...