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. Java虚拟机------JVM介绍

    Java平台和语言最开始只是SUN公司在1990年12月开始研究的一个内部项目: Java的平台无关性 Java平台和语言最开始只是SUN公司在1990年12月开始研究的一个内部项目[stealth ...

  2. scrapy-logging

    import logging logger = logging.getLogger(__name__) # 当前文件位置 logger.warning('haha') # debug info 201 ...

  3. mysql 表映射为java bean 手动生成。

    在日常工作中,一般是先建表.后建类.当然也有先UML构建类与类的层级关系,直接生成表.(建模)这里只针对先有表后有类的情况.不采用代码生成器的情况. 例如: 原表结构: ),)) BEGIN ); ) ...

  4. Tools:apache部署https服务

    转自:https://www.cnblogs.com/ccccwork/p/6529367.html 1.要搭建https,必须要具备的东西 1.超文本传输协议httpd(apache)和ssl模块( ...

  5. android toolbar使用记录

    1.打开Project structure,选择app modules,切换到Dependencies添加com.android.support.design.26.0.0.alpha1 2.在lay ...

  6. openvpn通过ldap或ad统一认证解决方案思路分享

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://oldboy.blog.51cto.com/2561410/986933 缘起:成 ...

  7. Delphi中Chrome Chromium、Cef3学习笔记(一)

    原文   http://blog.csdn.net/xtfnpgy/article/details/46635225   官方下载地址:https://cefbuilds.com/ CEF简介: 嵌入 ...

  8. python 列表、元组、字典的区别

    区别: 相互转换:https://www.cnblogs.com/louis-w/p/8391147.html 一.列表 list [1,[2,'AA'],5,'orderl'] 1.任意对象的有序集 ...

  9. redis安装--单机

    本例基于CentOS7.2系统安装 环境需求: 关闭防护墙,selinux 安装好gcc等编译需要的组件 yum -y install gcc c++ 到redis官网下载合适版本的redis安装包, ...

  10. 使用jQuery+huandlebars循环遍历中使用索引

    兼容ie8(很实用,复制过来,仅供技术参考,更详细内容请看源地址:http://www.cnblogs.com/iyangyuan/archive/2013/12/12/3471227.html) & ...