ABP應用層——應用服務(Application services)
基於DDD的現代ASP.NET開發框架--ABP系列之15、ABP應用層——應用服務(Application services)
ABP是“ASP.NET Boilerplate Project (ASP.NET樣板項目)”的簡稱。
ABP的官方網站:http://www.aspnetboilerplate.com
ABP在Github上的開源項目:https://github.com/aspnetboilerplate
本文由東莞-天道提供翻譯
應用服務用於將領域(業務)邏輯暴露給展現層。展現層通過傳入DTO(數據傳輸對象)參數來調用應用服務,而應用服務通過領域對象來執行相應的業務邏輯並且將DTO返回給展現層。因此,展現層和領域層將被完全隔離開來。在一個理想的層級項目中,展現層應該從不直接訪問領域對象。
IApplicationService接口
在ABP中,一個應用服務需要實現IApplicationService接口。最好的實踐是針對每個應用服務都創建相應的接口。所以,我們首先定義一個應用服務接口,如下所示:
public interface IPersonAppService : IApplicationService { void CreatePerson(CreatePersonInput input); }
IPersonAppService只有一個方法,它將被展現層調用來創建一個新的Person。CreatePersonInput是一個DTO對象,如下所示:
public class CreatePersonInput : IInputDto { [Required] public string Name { get; set; } public string EmailAddress { get; set; } }
接着,我們實現IPersonAppService接口:
public class PersonAppService : IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public void CreatePerson(CreatePersonInput input) { var person = _personRepository.FirstOrDefault(p => p.EmailAddress == input.EmailAddress); if (person != null) { throw new UserFriendlyException("There is already a person with given email address"); } person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); } }
以下是幾個重要提示:
- PersonAppService通過IRepository來執行數據庫操作。它通過構造器注入模式來生成。我們在這里使用了依賴注入。
- PersonAppService實現了IApplicationService(通過IPersonAppService繼承IApplicationService)。ABP會自動地把它注冊到依賴注入系統中,並可以注入到別的類型中使用。
- CreatePerson方法需要一個CreatePersonInput類型的參數。這是一個作為輸入的DTO,它將被ABP自動驗證其數據有效性。可以查看DTO和數據有效性驗證(Validation)文檔獲取相關細節。
應用服務類型
應用服務(Application Services)需要實現IApplicationService接口。當然,你可以選擇將你的應用服務(Application Services)繼承自ApplicationService基類,這樣你的應用服務也就自然而然的實現IApplicationService接口了。ApplicationService基類提供了方便的日志記錄和本地化功能。在此建議你針對你的應用程序創建一個應用服務基類繼承自ApplicationService類型。這樣你就可以添加一些公共的功能來提供給你的所有應用服務使用。一個應用服務示例如下所示:
public class TaskAppService : ApplicationService, ITaskAppService { public TaskAppService() { LocalizationSourceName = "SimpleTaskSystem"; } public void CreateTask(CreateTaskInput input) { //記錄日志,Logger定義在ApplicationService中 Logger.Info("Creating a new task with description: " + input.Description); //獲取本地化文本(L是LocalizationHelper.GetString(...)的簡便版本, 定義在 ApplicationService類型) var text = L("SampleLocalizableTextKey"); //TODO: Add new task to database... } }
本例中我們在構造函數中定義了LocalizationSourceName,但你可以在基類中定義它,這樣你就不需要在每個具體的應用服務中定義它。查看日志記錄(logging)和本地化(localization)文檔可以獲取更多的相關信息。
工作單元
在ABP中,一個應用服務方法默認是一個工作單元。
(1)連接 & 事務管理 (For connection & transaction management)
在應用服務方法中,如果我們需要調用兩個倉儲方法,那么這些方法必須為一個事務。舉個例子:
public void CreatePerson(CreatePersonInput input) { var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); _statisticsRepository.IncrementPeopleCount(); }
我們向Person表插入一個數據,接着在其他表中修改了Person計數字段的值。這兩個操作實現於不同的倉儲中,但是它們使用了相同的數據連接和事務。這是怎么實現的呢?
對於UOW模式,當事務啟動並且開始執行CreatePerson方法的時候,ABP會自動地打開數據庫。在方法結束時,如果未發生異常該事務將會被提交,並確保關閉數據庫連接。因此,CreatePerson方法中的所有數據庫操作將作為一個事務(具有原子性),當有異常拋出時這些事務中的操作將會回滾。所以,示例中的兩個倉儲方法使用了相同的數據連接和事務。
當你調用倉儲中的GetAll()方法時,它將返回一個IQueryable。數據庫連接應會在調用倉儲方法后打開。這是因為IQueryable和LINQ的延遲執行。當你調用類似ToList()方法時,數據庫查詢才會真正的開始執行。來看下面的示例:
public SearchPeopleOutput SearchPeople(SearchPeopleInput input) { //獲取 IQueryable<Person> var query = _personRepository.GetAll(); //過濾數據 if (!string.IsNullOrEmpty(input.SearchedName)) { query = query.Where(person => person.Name.StartsWith(input.SearchedName)); } if (input.IsActive.HasValue) { query = query.Where(person => person.IsActive == input.IsActive.Value); } //獲取分頁 var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList(); return new SearchPeopleOutput {People = Mapper.Map<List<PersonDto>>(people)}; }
由於一個應用服務(Application Services)方法就是一個工作單元,所以數據庫連接在方法執行期間都是開啟的。如果你在非應用服務(Application Services)中調用GetAll(),你需要顯式的使用工作單元模式。如:在Controller的Action方法中要使用GetAll()或調用多個有對數據庫操作的AppService方法時, 應該將Action方法使用virtual修飾,並在Action的上面通過[UnitOfWork]進行顯示開啟工作單元模式。
注意我使用了AutoMapper庫將List轉換成List。可以查看DTO文檔獲取相關細節。
譯者-天道注:這里要說一下,就是uow和非uow模式的區別,兩種模式對於數據庫連接的打開和關閉是不同的。對於控制器的方法,ABP默認是非 uow模式,此時如果調用方法會報錯,提示數據庫未連接。解決的辦法是在方法加上virtual。
(2)自動保存數據修改 (For automatically saving changes)
對於工作單元方法(應用服務(Application Services)方法),在方法結束時ABP將會自動保存所有數據修改。假設我們需要一個應用服務(Application Services)方法來更新一個Person的Name:
public void UpdateName(UpdateNameInput input) { var person = _personRepository.Get(input.PersonId); person.Name = input.NewName; }
就是這樣,Name被成功修改!我們甚至不需要調用_personRepository.Update方法。ORM框架在工作單元中會跟蹤所有實體修改並將修改更新到數據庫中。
應用服務的生命周期
所有應用服務(Application Services)實例的生命周期都是暫時的(Transient)。這意味着在每次使用都會創建新的應用服務(Application Services)實例。ABP堅決地使用依賴注入技術。當一個應用服務(Application Services)類型需要被注入時,該應用服務(Application Services)類型的新實例將會被依賴注入容器自動創建。查看依賴注入(Dependency Injection)文檔獲取更多信息。