點這裏進入ABP系列文章總目錄html
基於DDD的現代ASP.NET開發框架--ABP系列之1五、ABP應用層——應用服務(Application services)
git
ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。github
ABP的官方網站:http://www.aspnetboilerplate.com數據庫
ABP在Github上的開源項目:https://github.com/aspnetboilerplate架構
本文由東莞-天道提供翻譯app
應用服務用於將領域(業務)邏輯暴露給展示層。展示層經過傳入DTO(數據傳輸對象)參數來調用應用服務,而應用服務經過領域對象來執行相應的業務邏輯而且將DTO返回給展示層。所以,展示層和領域層將被徹底隔離開來。在一個理想的層級項目中,展示層應該從不直接訪問領域對象。框架
在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接口:ui
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); } }
如下是幾個重要提示:
應用服務(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中,一個應用服務方法默認是一個工做單元。
在應用服務方法中,若是咱們須要調用兩個倉儲方法,那麼這些方法必須爲一個事務。舉個例子:
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。
對於工做單元方法(應用服務(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)文檔獲取更多信息。
但願更多國內的架構師能關注到ABP這個項目,也許這其中有能幫助到您的地方,也許有您的參與,這個項目能夠發展得更好。
歡迎加QQ羣:
ABP架構設計交流羣:134710707 ABP架構設計交流2羣: 579765441