在前面隨筆《ABP開發框架前后端開發系列---(1)框架的總體介紹》大概介紹了這個ABP框架的主要特點,以及介紹了我對這框架的Web API應用優先的一些看法,本篇繼續探討ABP框架的初步使用,也就是我們下載到的ABP框架項目(基於ABP基礎項目的擴展項目),如果理解各個組件模塊,以及如何使用。
1)ABP框架應用項目的介紹
整個基礎的ABP框架看似非常龐大,其實很多項目也很少內容,主要是獨立封裝不同的組件進行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等內容,基本上我們主要關注的內容就是Abp這個主要的項目里面,其他的是針對不同的組件應用做的封裝。
而基於基礎ABP框架擴展出來的ABP應用項目,則簡單很多,我們也是在需要用到不同組件的時候,才考慮引入對應的基礎模塊進行使用,一般來說,主要還是基於倉儲管理實現基於數據庫的應用,因此我們主要對微軟的實體框架的相關內容了解清楚即可。
這個項目是一個除了包含基礎的人員、角色、權限、認證、配置信息的基礎項目外,而如果你從這里開始,對於其中的一些繼承關系的了解,會增加很多困難,因為它們基礎的用戶、角色等對象關系實在是很復雜。
我建議從一個簡單的項目開始,也就是基於一兩個特定的應用表開始的項目,因此可以參考案例項目:eventcloud 或者 sample-blog-module 項目,我們入門理解起來可能更加清楚。這里我以eventcloud項目來進行分析項目中各個層的類之間的關系。
我們先從一個關系圖來了解下框架下的領域驅動模塊中的各個類之間的關系。
先以領域層,也就是項目中的EventCloud.Core里面的內容進行分析。
2)領域對象層的代碼分析
首先,我們需要了解領域對象和數據庫之間的關系的類,也就是領域實體信息,這個類非常關鍵,它是構建倉儲模式和數據庫表之間的關系的。
[Table("AppEvents")] public class Event : FullAuditedEntity<Guid>, IMustHaveTenant { public virtual int TenantId { get; set; } [Required] [StringLength(MaxTitleLength)] public virtual string Title { get; protected set; } [StringLength(MaxDescriptionLength)] public virtual string Description { get; protected set; } public virtual DateTime Date { get; protected set; } public virtual bool IsCancelled { get; protected set; } ...... }
這個里面定義了領域實體和表名之間的關系,其他屬性也就是對應數據庫的字段了
[Table("AppEvents")]
然后在EventCloud.EntityFrameworkCore項目里面,加入這個表的DbSet對象,如下代碼所示。
namespace EventCloud.EntityFrameworkCore { public class EventCloudDbContext : AbpZeroDbContext<Tenant, Role, User, EventCloudDbContext> { public virtual DbSet<Event> Events { get; set; } public virtual DbSet<EventRegistration> EventRegistrations { get; set; } public EventCloudDbContext(DbContextOptions<EventCloudDbContext> options) : base(options) { } } }
簡單的話,倉儲模式就可以跑起來了,我們利用 IRepository<Event, Guid> 接口就可以獲取對應表的很多處理接口,包括增刪改查、分頁等等接口,不過為了進行業務邏輯的隔離,我們引入了Application Service應用層,同時也引入了DTO(數據傳輸對象)的概念,以便向應用層隱藏我們的領域對象信息,實現更加彈性化的處理。一般和領域對象對應的DTO對象定義如下所示。
[AutoMapFrom(typeof(Event))] public class EventListDto : FullAuditedEntityDto<Guid> { public string Title { get; set; } public string Description { get; set; } public DateTime Date { get; set; } public bool IsCancelled { get; set; } public virtual int MaxRegistrationCount { get; protected set; } public int RegistrationsCount { get; set; } }
其中我們需要注意實體類繼承自FullAuditedEntityDto<Guid>,它標記這個領域對象會記錄創建、修改、刪除的標記、時間和人員信息,如果需要深入了解這個部分,可以參考下ABP官網關於領域實體對象的介紹內容(Entities)。
通過在類增加標記性的特性處理,我們可以從Event領域對象到EventListDto的對象實現了自動化的映射。這樣的定義處理,一般來說沒有什么問題,但是如果我們需要把DTO(如EventListDto)隔離和領域對象(如Event)的關系,把DTO單獨抽取來方便公用,那么我們可以在應用服務層定義一個領域對象的映射文件來替代這種聲明式的映射關系,AutoMaper的映射文件定義如下所示。
public class EventMapProfile : Profile { public EventMapProfile() { CreateMap<EventListDto, Event>(); CreateMap<EventDetailOutput, Event>(); CreateMap<EventRegistrationDto, EventRegistration>(); } }
這樣抽取獨立的映射文件,可以為我們單獨抽取DTO對象和應用層接口作為一個獨立項目提供方便,因為不需要依賴領域實體。如我改造項目的DTO層實例如下所示。
剛才介紹了領域實體和DTO對象的映射關系,就是為了給應用服務層提供數據的承載。
如果領域對象的邏輯處理比較復雜一些,還可以定義一個類似業務邏輯類(類似我們說說的BLL),一般ABP框架里面以Manager結尾的就是這個概念,如對於案例里面,業務邏輯接口和邏輯類定義如下所示,這里注意接口繼承自IDomainService接口。
/// <summary> /// Event的業務邏輯類 /// </summary> public interface IEventManager: IDomainService { Task<Event> GetAsync(Guid id); Task CreateAsync(Event @event); void Cancel(Event @event); Task<EventRegistration> RegisterAsync(Event @event, User user); Task CancelRegistrationAsync(Event @event, User user); Task<IReadOnlyList<User>> GetRegisteredUsersAsync(Event @event); }
業務邏輯類的實現如下所示。
我們看到這個類的構造函數里面,帶入了幾個接口對象的參數,這個就是DI,依賴注入的概念,這些通過IOC容易進行構造函數的注入,我們只需要知道,在模塊啟動后,這些接口都可以使用就可以了,如果需要了解更深入的,可以參考ABP官網對於依賴注入的內容介紹(Dependency Injection)。
這樣我們對應的Application Service里面,對於Event的應用服務層的類EventAppService ,如下所示。
[AbpAuthorize] public class EventAppService : EventCloudAppServiceBase, IEventAppService { private readonly IEventManager _eventManager; private readonly IRepository<Event, Guid> _eventRepository; public EventAppService( IEventManager eventManager, IRepository<Event, Guid> eventRepository) { _eventManager = eventManager; _eventRepository = eventRepository; } ......
這里的服務層類提供了兩個接口注入,一個是自定義的事件業務對象類,一個是標准的倉儲對象。
大多數情況下如果是基於Web API的架構下,如果是基於數據庫表的處理,我覺得領域的業務管理類也是不必要的,直接使用倉儲的標准對象處理,已經可以滿足大多數的需要了,一些邏輯我們可以在Application Service里面實現以下即可。
3)字典模塊業務類的簡化
我們以字典模塊的字典類型表來介紹。
領域業務對象接口層定義如下所示(類似IBLL)
/// <summary> /// 領域業務管理接口 /// </summary> public interface IDictTypeManager : IDomainService { /// <summary> /// 獲取所有字典類型的列表集合(Key為名稱,Value為ID值) /// </summary> /// <param name="dictTypeId">字典類型ID,為空則返回所有</param> /// <returns></returns> Task<Dictionary<string, string>> GetAllType(string dictTypeId); }
領域業務對象管理類(類似BLL)
/// <summary> /// 領域業務管理類實現 /// </summary> public class DictTypeManager : DomainService, IDictTypeManager { private readonly IRepository<DictType, string> _dictTypeRepository; public DictTypeManager(IRepository<DictType, string> dictTypeRepository) { this._dictTypeRepository = dictTypeRepository; } /// <summary> /// 獲取所有字典類型的列表集合(Key為名稱,Value為ID值) /// </summary> /// <param name="dictTypeId">字典類型ID,為空則返回所有</param> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { IList<DictType> list = null; if (!string.IsNullOrWhiteSpace(dictTypeId)) { list = await _dictTypeRepository.GetAllListAsync(p => p.PID == dictTypeId); } else { list = await _dictTypeRepository.GetAllListAsync(); } Dictionary<string, string> dict = new Dictionary<string, string>(); foreach (var info in list) { if (!dict.ContainsKey(info.Name)) { dict.Add(info.Name, info.Id); } } return dict; } }
然后領域對象的應用服務層接口實現如下所示
[AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase<DictType, DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService { private readonly IDictTypeManager _manager; private readonly IRepository<DictType, string> _repository; public DictTypeAppService( IRepository<DictType, string> repository, IDictTypeManager manager) : base(repository) { _repository = repository; _manager = manager; } /// <summary> /// 獲取所有字典類型的列表集合(Key為名稱,Value為ID值) /// </summary> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { var result = await _manager.GetAllType(dictTypeId); return result; } ......
這樣就在應用服務層里面,就整合了業務邏輯類的處理,不過這樣的做法,對於常規數據庫的處理來說,顯得有點累贅,還需要多定義一個業務對象接口和一個業務對象實現,同時在應用層接口里面,也需要多增加一個接口參數,總體感覺有點多余,因此我把它改為使用標准的倉儲對象來處理就可以達到同樣的目的了。
在項目其中對應位置,刪除字典類型的一個業務對象接口和一個業務對象實現,改為標准倉儲對象的接口處理,相當於把業務邏輯里面的代碼提出來放在服務層而已,那么在應用服務層的處理代碼如下所示。
[AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase<DictType, DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService { private readonly IRepository<DictType, string> _repository; public DictTypeAppService( IRepository<DictType, string> repository) : base(repository) { _repository = repository; } /// <summary> /// 獲取所有字典類型的列表集合(Key為名稱,Value為ID值) /// </summary> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { IList<DictType> list = null; if (!string.IsNullOrWhiteSpace(dictTypeId)) { list = await Repository.GetAllListAsync(p => p.PID == dictTypeId); } else { list = await Repository.GetAllListAsync(); } Dictionary<string, string> dict = new Dictionary<string, string>(); foreach (var info in list) { if (!dict.ContainsKey(info.Name)) { dict.Add(info.Name, info.Id); } } return dict; } ......
這樣我們少定義兩個文件,以及減少協調業務類的代碼,代碼更加簡潔和容易理解,反正最終實現都是基於倉儲對象的接口調用。
另外,我們繼續了解項目,知道在Web.Host項目是我們Web API層啟動,且動態構建Web API層的服務層。它整合了Swagger對接口的測試使用。
// Swagger - Enable this line and the related lines in Configure method to enable swagger UI services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Title = "MyProject API", Version = "v1" }); options.DocInclusionPredicate((docName, description) => true); // Define the BearerAuth scheme that's in use options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme() { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", In = "header", Type = "apiKey" }); // Assign scope requirements to operations based on AuthorizeAttribute options.OperationFilter<SecurityRequirementsOperationFilter>(); });
啟動項目,我們可以看到Swagger的管理界面如下所示。