文檔目錄
本節內容:
簡介
領域服務(或服務)用來執行領域操作和業務規則。Eric Evans描述一個好的服務需要三個特點(在他的DDD書裡):
與應用服務獲取/返回DTO(數據傳輸對象)不同,領域服務獲取/返回領域對象(如實體或值類型)。
領域服務可被應用服務或其它領域服務調用,但不直接被展現層(應用服務是針對它的)使用。
IDomainService 接口和DomainService 服務
ABP定義了IDomainService接口,按約定所有的領域服務都要實現它,實現之後,領域服務被自動暫時的注冊到依賴注入系統。
同樣,領域服務(隨意地)可以從DomainService類繼承,因此它可以使用繼承得來的日志、本地化、等屬性。即使不繼承DomainService類,也可以在需要時注入它。
例子
假設我們有一個任務管理系統,並有分配任務給人的業務規則。
創建一個接口
首先,我們為服務定義一個接口(不是必需,但是一個好的實踐):
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; } }
此處我們有兩個業務規則:
你可能想知道為什麼我在第一個檢查裡拋出一個ApplicationException,在第二個檢查裡拋出UserFriendlyException(見異常處理),這跟領域服務毫不相干,我這麼做只是舉個例子,具體怎麼做完全取決於你。我認為用戶接口必須檢查一個Task的狀態並且不應該分配給一個Person,我認為這是一個應用錯誤,應當對用戶隱藏。第二個是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); } }
TaskApplicationService使用給定的DTO(輸入)和倉儲來獲取相關的task和person,然後把它們傳遞給TaskManager(領域服務)。
相關論述
根據以上例子,你可能有些問題想問。
為什麼不只用應用服務?
也就是說為什麼應用服務不實現領域服務裡的業務邏輯?
我們可以簡單的這麼說:它不是應用服務的任務,因為它不是一個使用案例,而是一個業務操作。我們可能在另一個使用案例裡使用同一個“分配任務給人員”的領域邏輯,例如:我們在另一個界面上,用某種方式更新task,這個更新包含分配任務給另一個人員;我們可能有兩個不同的UI(一個移動應用和一個Web應用)共享相同的領域;或是一個為遠程客戶端提供分配task操作的Web Api等。
如果你的領域很簡單, 只有一個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; } //...other members and codes of Task entity public void AssignToPerson(Person person, ITaskPolicy taskPolicy) { taskPolicy.CheckIfCanAssignTaskToPerson(this, person); AssignedPersonId = person.Id; } }
我們把AssignedPersonId的設置器修改成protected,所以在實體類外就不能修改它。添加一個AssignToPerson方法,接受一個Person和一個TaskPolicy。CheckIfCanAssignTaskToPerson方法檢查是否可以分配,並在不能分配時拋出一個對應的異常(此處如何實現不重要)。然後應用服務如下所示:
public void AssignTaskToPerson(AssignTaskToPersonInput input) { var task = _taskRepository.Get(input.TaskId); var person = _personRepository.Get(input.PersonId); task.AssignToPerson(person, _taskPolicy); }
我們把_taskPolicy注入給ITaskPolicy並傳給AssignToPerson方法。至此就沒有第二方式能把一個任務分配給一個人員了,我們只能使用AssignToPerson,再也跳不過業務規則。