基於DDD的.NET開發框架 - ABP領域服務

返回ABP系列html

ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。git

ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成爲一個通用的WEB應用程序框架和項目模板。github

ABP的官方網站:http://www.aspnetboilerplate.comweb

ABP官方文檔:http://www.aspnetboilerplate.com/Pages/Documents框架

Github上的開源項目:https://github.com/aspnetboilerplate網站

1、概念介紹this

領域服務(或DDD中的服務)用於執行領域操做和業務規則。Eric Evans描述了一個好的服務應該具有下面三個特徵:spa

一、和領域概念相關的操做不是一個實體或者值對象的本質部分。設計

二、接口定義在領域模型其餘元素的條款中。code

三、操做是無狀態的。

跟得到或返回一個數據傳輸對象的應用服務方法(DTO)不一樣,領域服務得到或者返回一個領域對象(好比實體或值類型)。

一個領域服務能夠用於應用服務,也能夠用於其餘的領域服務,但不能直接用於展示層,服務層才直接用於展示層。

 

2、IDomainService接口和DomainService類

ABP定義了IDomainService接口,全部的領域服務都按照慣例實現了該接口。當實現時,領域服務會以transient自動註冊到依賴注入系統。

此外,領域服務(可選地)能夠從DomainService類繼承。所以,它可使用一些繼承的屬性,好比logging,本地化等等。固然,若是沒有繼承,若是須要的話也能夠注入這些屬性。

 

3、示例

假設咱們有一個任務管理系統而且有將一個任務派給一我的的業務規則。

一、建立一個接口

首先咱們爲該服務定義一個接口(不是必須的,可是這樣是一個好的實踐):

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

能夠看到,TaskManager服務使用領域對象工做:一個Task 和一個Person。命名領域服務時存在一些慣例。它能夠是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;
    }
}

上面的代碼定義了兩個業務規則:

    一個任務爲了可以派給一個新人,它應該是Active(激活)的狀態
    一我的能夠最多能夠有3個激活的任務。

你可能想知道爲啥第一次檢測時拋出了一個ApplicationException,而第二次檢查時拋出了UserFriendlyException,請關注後面博客的異常處理。這根領域服務根本無關。這裏這樣處理的想法是這樣的,UI必須先要檢查一個任務的狀態,不然不該該容許咱們將它派給一我的。這是一個應用程序的錯誤,而且咱們能夠向用戶隱藏這個錯誤。對於第二個友好的異常信息,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);
    }
}

Task應用服務使用給定的DTO(輸入)和倉儲來檢索相關的task和 person,並將它們傳給 TaskManager(領域服務)。

 

4、討論

基於上面的例子,你可能會存在下面的疑問。

一、爲什麼不僅使用應用服務

咱們能夠簡單地說,它不是應用服務要乾的活。由於領域邏輯不是一個用例(use-case),而是一個 業務操做。咱們能夠在不一樣的用例中使用相同的「將一個任務派給一我的」的邏輯。好比說咱們之後會更新這個任務,而且將這個任務派給其餘人。所以,咱們可使用相同的領域邏輯,這個邏輯就是「將一個任務派給一我的」,咱們不用考慮這個具體的人和具體的任務。此外,咱們可能有兩個不一樣的UI(一個移動端應用和一個web應用)來共享相同的領域。

應用服務層中有一個應用服務方法,但卻使用到了領域層的三個業務邏輯,由於在領域層中,獲取單個task和person都各自爲一個業務邏輯,將一個任務派給一我的又是一個業務邏輯。在應用服務層,咱們只須要得到一我的和一個任務就行,而後將該任務派給這我的,根本不須要考慮這我的和這個任務的獲取細節,也不用考慮任務派發的細節,由於這徹底不是應用層考慮的事兒。

若是你的領域很簡單,只有一個UI而且將一個任務派發給一我的在單點處就能夠完成,那麼你能夠跳過領域服務,而後在應用服務層實現該邏輯。雖然這不是DDD的最佳實踐,可是ABP不會強制你這麼設計。

二、如何強制使用領域服務

你能夠看到,應用服務只能作下面的事情:

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方法,並且不能跳過業務規則了。