今天談談《構建應用層服務》。
理論學習:
應用服務提供了一些門面樣式方法來分離表現層和領域層。這樣做的目的也是為了解耦,以後表現層就不用直接和業務邏輯層(核心層)打交道了,而是通過應用服務層(相當於媒介)來處理。應用服務層不僅定義了很多服務方法供表現層直接呼叫,而且還提供了一些Dtos(Data Transfer Object)。
說到Dto,好處確實挺多的。
第一,可以將它理解為一個簡化的實體,更方便。比如,我們有一個User表,裡面有Id,Name,Password,IsDeleted,CreationTime等等。那麼我們簡化的UserDto物件就只含有Name和IsDeleted兩個欄位就夠用了,因為表現層就只用到了這兩個欄位。
第二,更安全,效能更好。如果不用Dto而用實體類的話,最後生成的查詢就會將實體的所有欄位都會查詢出來。這樣一來就暴露了一些重要的資料給一些我們不想這些資料被看到的人。
第三,應用擴充套件性更好,耦合度降低。表現層是通過一個Dto物件作為引數來呼叫應用服務方法的,然後使用領域物件(實體類物件)執行特定的業務邏輯並返回一個Dto物件給表現層,所以,表現層完全獨立於領域層。在一個理想的應用中,表現層和領域層不會直接打交道。
第四,序列化問題。當返回一個資料物件到表現層時,很可能會在某個地方序列化。比如,在一個返回JSON的MVC方法中,你的物件可能會被序列化成Json傳送到客戶端。如果返回一個實體的話會有問題的。比如,這個User實體有一個Role的應用,如果要序列化User的話也會序列化Role。甚至Role可能有List<Permission>,Permission又有一個PermissionGroup的引用等等…你敢想象序列化之後的物件嗎?亦可以輕易地一下子序列化整個資料庫。所以,在這種情況下返回一個安全序列化的、特別設計的DTOs是一個好的做法。
先來定義Dtos,看下面程式碼:
namespace Noah.ChargeStation.Application.CitiesApp.Dto { public class CityInput : IInputDto { public string Name { get; set; } public string Code { get; set; } public string ProvinceCode { get; set; } } public class GetCityInput : IInputDto { public string Name { get; set; } public string ProvinceCode { get; set; } } public class CreateCityInput : IInputDto, IShouldNormalize { [Required] public string Name { get; set; } [Required] public string Code { get; set; } [Required] public string ProvinceCode { get; set; } public DateTime UpdatedTime { get; set; } public string UpdatedBy { get; set; } public void Normalize() { if (UpdatedTime==null) { UpdatedTime=DateTime.Now; } } } }
這個是輸入方向的Dto,實現了IInputDto介面,這樣的話ABP可以自動幫助我們進行資料校驗。當然,我們也可以新增資料註解進行校驗。校驗之後,還可以實現IShouldNormalize介面來設定預設值。
namespace Noah.ChargeStation.Application.CitiesApp.Dto { public class CityOutput:IOutputDto { public string Code { get; set; } public string Name { get; set; } public string ProvinceCode { get; set; } } public class GetCitiesOutput:IOutputDto { public List<CityDto> Cities { get; set; } } }
以上是輸出方向的Dto。
接下來我定義一個城市表的服務介面ICityAppService,我的命名規範是”I+實體類單數+AppService”。
namespace Noah.ChargeStation.Application.CitiesApp { public interface ICityAppService:IApplicationService { GetCitiesOutput GetCities(GetCityInput input); Task<GetCitiesOutput> GetCitiesAsync(GetCityInput input); void UpdateCity(CityInput input); Task UpdateCityAsync(CityInput input); void CreateCity(CityInput input); Task CreateCityAsync(CityInput input); } }
以上定義的方法有同步和非同步兩個版本。
接下來實現應用服務介面,這裡只實現一個方法GetCities(…),望讀者舉一反三。
public class CityAppService : ChargeStationAppServiceBase, ICityAppService { private readonly IRepository<Cities> _cityRepository; public CityAppService(IRepository<Cities> cityRepository) { _cityRepository = cityRepository; } public GetCitiesOutput GetCities(GetCityInput input) { //根據不同條件進行查詢不同的結果 //Mapper.CreateMap<Cities, CityOutput>(); //根據城市名稱查詢城市資料 if (!string.IsNullOrEmpty(input.Name)) { var cityEntity = _cityRepository.GetAllList(c => c.Name == input.Name).FirstOrDefault(); return new GetCitiesOutput() { CityDto = Mapper.Map<CityDto>(cityEntity) }; } //根據省份編碼查詢城市資料 if (!string.IsNullOrEmpty(input.ProvinceCode)) { var cityEntityList = _cityRepository.GetAllList(c => c.ProvinceCode == input.ProvinceCode); return new GetCitiesOutput() { CityDtoList = Mapper.Map<List<CityDto>>(cityEntityList) }; } return null; } public void UpdateCity(CityInput input) { Logger.Info("Updating a City for input: " + input); } public void CreateCity(CityInput input) { //var city = _cityRepository.FirstOrDefault(c => c.Name == input.Name); //if (city != null) //{ // throw new UserFriendlyException("該城市資料已經存在!"); //} //city = new City() { Code = input.Code, Name = input.Name, ProvinceCode = input.ProvinceCode }; //_cityRepository.Insert(city); } public Task<GetCitiesOutput> GetCitiesAsync(GetCityInput input) { throw new System.NotImplementedException(); } public Task UpdateCityAsync(CityInput input) { throw new System.NotImplementedException(); } public Task CreateCityAsync(CityInput input) { throw new System.NotImplementedException(); } }
這裡又出現了個新東西AutoMapper,關於這個的使用,我這幾天會專門開一個關於AutoMapper的專題,敬請期待。程式碼沒有難度,也就不多做解釋了。今天就到這裡吧。下次再講。