在前面隨筆《ABP開發(fā)框架前后端開發(fā)系列---(1)框架的總體介紹》大概介紹了這個(gè)ABP框架的主要特點(diǎn),以及介紹了我對(duì)這框架的Web API應(yīng)用優(yōu)先的一些看法氛雪,本篇繼續(xù)探討ABP框架的初步使用房匆,也就是我們下載到的ABP框架項(xiàng)目(基于ABP基礎(chǔ)項(xiàng)目的擴(kuò)展項(xiàng)目),如果理解各個(gè)組件模塊报亩,以及如何使用浴鸿。
1)ABP框架應(yīng)用項(xiàng)目的介紹
整個(gè)基礎(chǔ)的ABP框架看似非常龐大,其實(shí)很多項(xiàng)目也很少內(nèi)容弦追,主要是獨(dú)立封裝不同的組件進(jìn)行使用岳链,如Automaper、SignalR劲件、MongoDB掸哑、Quartz。零远。苗分。等等內(nèi)容,基本上我們主要關(guān)注的內(nèi)容就是Abp這個(gè)主要的項(xiàng)目里面牵辣,其他的是針對(duì)不同的組件應(yīng)用做的封裝摔癣。
而基于基礎(chǔ)ABP框架擴(kuò)展出來(lái)的ABP應(yīng)用項(xiàng)目,則簡(jiǎn)單很多纬向,我們也是在需要用到不同組件的時(shí)候择浊,才考慮引入對(duì)應(yīng)的基礎(chǔ)模塊進(jìn)行使用,一般來(lái)說逾条,主要還是基于倉(cāng)儲(chǔ)管理實(shí)現(xiàn)基于數(shù)據(jù)庫(kù)的應(yīng)用琢岩,因此我們主要對(duì)微軟的實(shí)體框架的相關(guān)內(nèi)容了解清楚即可。
這個(gè)項(xiàng)目是一個(gè)除了包含基礎(chǔ)的人員师脂、角色担孔、權(quán)限江锨、認(rèn)證、配置信息的基礎(chǔ)項(xiàng)目外攒磨,而如果你從這里開始泳桦,對(duì)于其中的一些繼承關(guān)系的了解,會(huì)增加很多困難娩缰,因?yàn)樗鼈兓A(chǔ)的用戶灸撰、角色等對(duì)象關(guān)系實(shí)在是很復(fù)雜。
我建議從一個(gè)簡(jiǎn)單的項(xiàng)目開始拼坎,也就是基于一兩個(gè)特定的應(yīng)用表開始的項(xiàng)目浮毯,因此可以參考案例項(xiàng)目:eventcloud 或者 sample-blog-module 項(xiàng)目,我們?nèi)腴T理解起來(lái)可能更加清楚泰鸡。這里我以eventcloud項(xiàng)目來(lái)進(jìn)行分析項(xiàng)目中各個(gè)層的類之間的關(guān)系债蓝。
我們先從一個(gè)關(guān)系圖來(lái)了解下框架下的領(lǐng)域驅(qū)動(dòng)模塊中的各個(gè)類之間的關(guān)系。
先以領(lǐng)域?qū)邮⒘洌簿褪琼?xiàng)目中的EventCloud.Core里面的內(nèi)容進(jìn)行分析饰迹。
2)領(lǐng)域?qū)ο髮拥拇a分析
首先,我們需要了解領(lǐng)域?qū)ο蠛蛿?shù)據(jù)庫(kù)之間的關(guān)系的類余舶,也就是領(lǐng)域?qū)嶓w信息啊鸭,這個(gè)類非常關(guān)鍵,它是構(gòu)建倉(cāng)儲(chǔ)模式和數(shù)據(jù)庫(kù)表之間的關(guān)系的匿值。
[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; }
......
}
這個(gè)里面定義了領(lǐng)域?qū)嶓w和表名之間的關(guān)系赠制,其他屬性也就是對(duì)應(yīng)數(shù)據(jù)庫(kù)的字段了
[Table("AppEvents")]
然后在EventCloud.EntityFrameworkCore項(xiàng)目里面,加入這個(gè)表的DbSet對(duì)象挟憔,如下代碼所示钟些。
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)
{
}
}
}
簡(jiǎn)單的話,倉(cāng)儲(chǔ)模式就可以跑起來(lái)了绊谭,我們利用 IRepository<Event, Guid> 接口就可以獲取對(duì)應(yīng)表的很多處理接口政恍,包括增刪改查、分頁(yè)等等接口达传,不過為了進(jìn)行業(yè)務(wù)邏輯的隔離篙耗,我們引入了Application Service應(yīng)用層,同時(shí)也引入了DTO(數(shù)據(jù)傳輸對(duì)象)的概念趟大,以便向應(yīng)用層隱藏我們的領(lǐng)域?qū)ο笮畔ⅲ瑢?shí)現(xiàn)更加彈性化的處理铣焊。一般和領(lǐng)域?qū)ο髮?duì)應(yīng)的DTO對(duì)象定義如下所示逊朽。
[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; }
}
其中我們需要注意實(shí)體類繼承自FullAuditedEntityDto<Guid>,它標(biāo)記這個(gè)領(lǐng)域?qū)ο髸?huì)記錄創(chuàng)建曲伊、修改叽讳、刪除的標(biāo)記追他、時(shí)間和人員信息,如果需要深入了解這個(gè)部分岛蚤,可以參考下ABP官網(wǎng)關(guān)于領(lǐng)域?qū)嶓w對(duì)象的介紹內(nèi)容(Entities)邑狸。
通過在類增加標(biāo)記性的特性處理,我們可以從Event領(lǐng)域?qū)ο蟮紼ventListDto的對(duì)象實(shí)現(xiàn)了自動(dòng)化的映射涤妒。這樣的定義處理单雾,一般來(lái)說沒有什么問題,但是如果我們需要把DTO(如EventListDto)隔離和領(lǐng)域?qū)ο螅ㄈ鏓vent)的關(guān)系她紫,把DTO單獨(dú)抽取來(lái)方便公用硅堆,那么我們可以在應(yīng)用服務(wù)層定義一個(gè)領(lǐng)域?qū)ο蟮挠成湮募?lái)替代這種聲明式的映射關(guān)系,AutoMaper的映射文件定義如下所示贿讹。
public class EventMapProfile : Profile
{
public EventMapProfile()
{
CreateMap<EventListDto, Event>();
CreateMap<EventDetailOutput, Event>();
CreateMap<EventRegistrationDto, EventRegistration>();
}
}
這樣抽取獨(dú)立的映射文件渐逃,可以為我們單獨(dú)抽取DTO對(duì)象和應(yīng)用層接口作為一個(gè)獨(dú)立項(xiàng)目提供方便,因?yàn)椴恍枰蕾囶I(lǐng)域?qū)嶓w民褂。如我改造項(xiàng)目的DTO層實(shí)例如下所示茄菊。
剛才介紹了領(lǐng)域?qū)嶓w和DTO對(duì)象的映射關(guān)系,就是為了給應(yīng)用服務(wù)層提供數(shù)據(jù)的承載赊堪。
如果領(lǐng)域?qū)ο蟮倪壿嬏幚肀容^復(fù)雜一些面殖,還可以定義一個(gè)類似業(yè)務(wù)邏輯類(類似我們說說的BLL),一般ABP框架里面以Manager結(jié)尾的就是這個(gè)概念雹食,如對(duì)于案例里面畜普,業(yè)務(wù)邏輯接口和邏輯類定義如下所示,這里注意接口繼承自IDomainService接口群叶。
/// <summary>
/// Event的業(yè)務(wù)邏輯類
/// </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);
}
業(yè)務(wù)邏輯類的實(shí)現(xiàn)如下所示吃挑。
我們看到這個(gè)類的構(gòu)造函數(shù)里面,帶入了幾個(gè)接口對(duì)象的參數(shù)街立,這個(gè)就是DI舶衬,依賴注入的概念,這些通過IOC容易進(jìn)行構(gòu)造函數(shù)的注入赎离,我們只需要知道逛犹,在模塊啟動(dòng)后,這些接口都可以使用就可以了梁剔,如果需要了解更深入的虽画,可以參考ABP官網(wǎng)對(duì)于依賴注入的內(nèi)容介紹(Dependency Injection)。
這樣我們對(duì)應(yīng)的Application Service里面荣病,對(duì)于Event的應(yīng)用服務(wù)層的類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;
}
......
這里的服務(wù)層類提供了兩個(gè)接口注入个盆,一個(gè)是自定義的事件業(yè)務(wù)對(duì)象類脖岛,一個(gè)是標(biāo)準(zhǔn)的倉(cāng)儲(chǔ)對(duì)象朵栖。
大多數(shù)情況下如果是基于Web API的架構(gòu)下,如果是基于數(shù)據(jù)庫(kù)表的處理柴梆,我覺得領(lǐng)域的業(yè)務(wù)管理類也是不必要的陨溅,直接使用倉(cāng)儲(chǔ)的標(biāo)準(zhǔn)對(duì)象處理,已經(jīng)可以滿足大多數(shù)的需要了绍在,一些邏輯我們可以在Application Service里面實(shí)現(xiàn)以下即可门扇。
3)字典模塊業(yè)務(wù)類的簡(jiǎn)化
我們以字典模塊的字典類型表來(lái)介紹。
領(lǐng)域業(yè)務(wù)對(duì)象接口層定義如下所示(類似IBLL)
/// <summary>
/// 領(lǐng)域業(yè)務(wù)管理接口
/// </summary>
public interface IDictTypeManager : IDomainService
{
/// <summary>
/// 獲取所有字典類型的列表集合(Key為名稱揣苏,Value為ID值)
/// </summary>
/// <param name="dictTypeId">字典類型ID悯嗓,為空則返回所有</param>
/// <returns></returns>
Task<Dictionary<string, string>> GetAllType(string dictTypeId);
}
領(lǐng)域業(yè)務(wù)對(duì)象管理類(類似BLL)
/// <summary>
/// 領(lǐng)域業(yè)務(wù)管理類實(shí)現(xiàn)
/// </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;
}
}
然后領(lǐng)域?qū)ο蟮膽?yīng)用服務(wù)層接口實(shí)現(xiàn)如下所示
[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;
}
......
這樣就在應(yīng)用服務(wù)層里面,就整合了業(yè)務(wù)邏輯類的處理坑质,不過這樣的做法合武,對(duì)于常規(guī)數(shù)據(jù)庫(kù)的處理來(lái)說,顯得有點(diǎn)累贅涡扼,還需要多定義一個(gè)業(yè)務(wù)對(duì)象接口和一個(gè)業(yè)務(wù)對(duì)象實(shí)現(xiàn)稼跳,同時(shí)在應(yīng)用層接口里面,也需要多增加一個(gè)接口參數(shù)吃沪,總體感覺有點(diǎn)多余汤善,因此我把它改為使用標(biāo)準(zhǔn)的倉(cāng)儲(chǔ)對(duì)象來(lái)處理就可以達(dá)到同樣的目的了。
在項(xiàng)目其中對(duì)應(yīng)位置票彪,刪除字典類型的一個(gè)業(yè)務(wù)對(duì)象接口和一個(gè)業(yè)務(wù)對(duì)象實(shí)現(xiàn)红淡,改為標(biāo)準(zhǔn)倉(cāng)儲(chǔ)對(duì)象的接口處理,相當(dāng)于把業(yè)務(wù)邏輯里面的代碼提出來(lái)放在服務(wù)層而已降铸,那么在應(yīng)用服務(wù)層的處理代碼如下所示在旱。
[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;
}
......
這樣我們少定義兩個(gè)文件推掸,以及減少協(xié)調(diào)業(yè)務(wù)類的代碼桶蝎,代碼更加簡(jiǎn)潔和容易理解,反正最終實(shí)現(xiàn)都是基于倉(cāng)儲(chǔ)對(duì)象的接口調(diào)用谅畅。
另外登渣,我們繼續(xù)了解項(xiàng)目,知道在Web.Host項(xiàng)目是我們Web API層啟動(dòng)毡泻,且動(dòng)態(tài)構(gòu)建Web API層的服務(wù)層胜茧。它整合了Swagger對(duì)接口的測(cè)試使用。
// 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>();
});
啟動(dòng)項(xiàng)目牙捉,我們可以看到Swagger的管理界面如下所示竹揍。