標籤:http href 共用 資訊 相關 ssi ddd 應用服務 domain
文檔目錄
本節內容:
- 簡介
- 例子
- 相關論述
- 為什麼不只用應用服務?
- 如何強制你使用領網域服務?
簡介
領網域服務(或服務)用來執行領網域作業和商務規則。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; }}
此處我們有兩個商務規則:
- 一個Task應當處於活躍狀態,才能分配給一個新的Person。
- 一個Person最多可以分配3個活躍Task
你可能想知道為什麼我在第一個檢查裡拋出一個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(一個行動裝置 App和一個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,再也跳不過商務規則。
<<ABP架構>> 領網域服務