领域服务

介绍

领域服务(或仅DDD中的服务)用于执行域操作和业务规则。在他的DDD书中,Eric Evans描述了一个有三个特征的优质服务:

  1. 操作涉及域概念,该概念不是实体或值对象的自然部分。
  2. 所述接口在所述的其它元件来定义域模型
  3. 这项行动是无状态的

 获取/返回数据传输对象的应用程序服务不同,域服务获取/返回域对象(如 实体或值类型)。

领域服务可以由应用程序服务和其他域服务使用,但不能直接由表示层使用(应用程序服务就是为此)。

IDomainService接口和DomainService类

ASP.NET Boilerplate定义了传统上由所有领域服务实现IDomainService接口实现后,域服务会自动注册到 依赖注入系统作为 瞬态

域服务可以选择从DomainService类继承有了它,您可以使用一些继承属性的强大功能进行日志记录,本地化等...

即使您不继承,也可以在需要时注入。

假设我们有一个任务管理系统,并且在为一个人分配任务时我们有一些业务规则。

创建接口

首先,我们为服务定义一个接口(不是必需的,但是很好的做法):

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

如您所见,TaskManager服务使用域对象: 任务人员命名域服务时有一些约定。它可以是TaskManager,TaskService或TaskDomainService ......

服务实施

让我们看看实现:

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

我们这里有两个业务规则:

想知道为什么我们为第一次检查抛出一个ApplicationException为第二次检查抛出UserFriendlyException(参见异常处理)?这根本与域服务无关。这只是一个例子,完全取决于你。用户界面必须检查任务的状态,不应允许我们将其分配给某个人。这是应用程序级错误,我们可能希望将其隐藏起来。

第二个例外更难以通过UI检查,因此我们将向用户显示可读的错误消息。

例如:

使用应用程序服务中的域服务

现在,让我们看看如何从应用程序服务中使用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);
    }
}

任务应用程序服务使用给定的DTO(输入),然后使用 存储库来检索相关的任务人员最后,它将它们传递给任务管理器(域服务)。

一些讨论

根据上面的示例,您可能会遇到一些问题。

为什么不只使用应用服务?

您可能想知道为什么应用程序服务本身不实现域服务中包含的逻辑。

我们可以简单地说它不是应用程序服务任务。因为它不是一个用例,而是一个业务操作,我们最终可能会在不同的用例中使用相同的“将任务分配给用户”域逻辑。假设我们有另一个屏幕以某种方式更新任务。此更新可以包括将任务分配给另一个人。我们可以在那里使用相同的域逻辑。我们可能还有2个不同的UI(一个移动应用程序和一个Web应用程序)共享同一个域,或者我们可能有一个包含任务分配操作的远程客户端的Web API。

如果您的域很简单,只有一个UI,并且只能在一个点上为一个人分配任务,那么您可以考虑跳过域服务并在应用程序服务中实现逻辑。这不是DDD的最佳实践,但ASP.NET Boilerplate不会强迫您使用这样的设计。

我们如何强制使用域名服务?

您可以看到应用程序服务可以简单地执行以下操作:

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

    //...other members and codes of Task entity

    public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
    {
        taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
        AssignedPersonId = person.Id;
    }
}

我们将AssignedPersonId的setter更改protected它不能在此Task实体类之外进行更改。我们添加了一个AssignToPerson方法,该 方法接受一个人和一个任务策略。该 CheckIfCanAssignTaskToPerson如果它是一个有效的分配方法检查,并抛出一个适当的异常如果不是(它的实现在这里并不重要)。应用程序服务方法如下所示:

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

    task.AssignToPerson(person, _taskPolicy);
}

我们将ITaskPolicy注入_taskPolicy并将其传递给AssignToPerson方法。现在没有第二种方法可以将任务分配给一个人。我们必须使用AssignToPerson,因此我们不能跳过业务规则。

nidie.com.cn - 用心与你沟通