ABP開發(fā)框架前后端開發(fā)系列---(2)框架的初步介紹

在前面隨筆《ABP開發(fā)框架前后端開發(fā)系列---(1)框架的總體介紹》大概介紹了這個ABP框架的主要特點,以及介紹了我對這框架的Web API應用優(yōu)先的一些看法,本篇繼續(xù)探討ABP框架的初步使用,也就是我們下載到的ABP框架項目(基于ABP基礎項目的擴展項目),如果理解各個組件模塊,以及如何使用。

1)ABP框架應用項目的介紹

整個基礎的ABP框架看似非常龐大,其實很多項目也很少內(nèi)容,主要是獨立封裝不同的組件進行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等內(nèi)容,基本上我們主要關注的內(nèi)容就是Abp這個主要的項目里面,其他的是針對不同的組件應用做的封裝。

image

而基于基礎ABP框架擴展出來的ABP應用項目,則簡單很多,我們也是在需要用到不同組件的時候,才考慮引入對應的基礎模塊進行使用,一般來說,主要還是基于倉儲管理實現(xiàn)基于數(shù)據(jù)庫的應用,因此我們主要對微軟的實體框架的相關內(nèi)容了解清楚即可。

image

這個項目是一個除了包含基礎的人員、角色、權(quán)限、認證、配置信息的基礎項目外,而如果你從這里開始,對于其中的一些繼承關系的了解,會增加很多困難,因為它們基礎的用戶、角色等對象關系實在是很復雜。

我建議從一個簡單的項目開始,也就是基于一兩個特定的應用表開始的項目,因此可以參考案例項目:eventcloud 或者 sample-blog-module 項目,我們?nèi)腴T理解起來可能更加清楚。這里我以eventcloud項目來進行分析項目中各個層的類之間的關系。

image

我們先從一個關系圖來了解下框架下的領域驅(qū)動模塊中的各個類之間的關系。

image

先以領域?qū)樱簿褪琼椖恐械腅ventCloud.Core里面的內(nèi)容進行分析。

2)領域?qū)ο髮拥拇a分析

首先,我們需要了解領域?qū)ο蠛蛿?shù)據(jù)庫之間的關系的類,也就是領域?qū)嶓w信息,這個類非常關鍵,它是構(gòu)建倉儲模式和數(shù)據(jù)庫表之間的關系的。

    [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; }
     
        ......
   }

這個里面定義了領域?qū)嶓w和表名之間的關系,其他屬性也就是對應數(shù)據(jù)庫的字段了

[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> 接口就可以獲取對應表的很多處理接口,包括增刪改查、分頁等等接口,不過為了進行業(yè)務邏輯的隔離,我們引入了Application Service應用層,同時也引入了DTO(數(shù)據(jù)傳輸對象)的概念,以便向應用層隱藏我們的領域?qū)ο笮畔ⅲ瑢崿F(xiàn)更加彈性化的處理。一般和領域?qū)ο髮腄TO對象定義如下所示。

    [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>,它標記這個領域?qū)ο髸涗泟?chuàng)建、修改、刪除的標記、時間和人員信息,如果需要深入了解這個部分,可以參考下ABP官網(wǎng)關于領域?qū)嶓w對象的介紹內(nèi)容(Entities)。

通過在類增加標記性的特性處理,我們可以從Event領域?qū)ο蟮紼ventListDto的對象實現(xiàn)了自動化的映射。這樣的定義處理,一般來說沒有什么問題,但是如果我們需要把DTO(如EventListDto)隔離和領域?qū)ο螅ㄈ鏓vent)的關系,把DTO單獨抽取來方便公用,那么我們可以在應用服務層定義一個領域?qū)ο蟮挠成湮募硖娲@種聲明式的映射關系,AutoMaper的映射文件定義如下所示。

    public class EventMapProfile : Profile
    {
        public EventMapProfile()
        {
            CreateMap<EventListDto, Event>();
            CreateMap<EventDetailOutput, Event>();
            CreateMap<EventRegistrationDto, EventRegistration>();
        }
    }

這樣抽取獨立的映射文件,可以為我們單獨抽取DTO對象和應用層接口作為一個獨立項目提供方便,因為不需要依賴領域?qū)嶓w。如我改造項目的DTO層實例如下所示。

image

剛才介紹了領域?qū)嶓w和DTO對象的映射關系,就是為了給應用服務層提供數(shù)據(jù)的承載。

如果領域?qū)ο蟮倪壿嬏幚肀容^復雜一些,還可以定義一個類似業(yè)務邏輯類(類似我們說說的BLL),一般ABP框架里面以Manager結(jié)尾的就是這個概念,如對于案例里面,業(yè)務邏輯接口和邏輯類定義如下所示,這里注意接口繼承自IDomainService接口。


    /// <summary>
    /// Event的業(yè)務邏輯類
    /// </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è)務邏輯類的實現(xiàn)如下所示。

image

我們看到這個類的構(gòu)造函數(shù)里面,帶入了幾個接口對象的參數(shù),這個就是DI,依賴注入的概念,這些通過IOC容易進行構(gòu)造函數(shù)的注入,我們只需要知道,在模塊啟動后,這些接口都可以使用就可以了,如果需要了解更深入的,可以參考ABP官網(wǎng)對于依賴注入的內(nèi)容介紹(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;
        }

        ......

這里的服務層類提供了兩個接口注入,一個是自定義的事件業(yè)務對象類,一個是標準的倉儲對象。

大多數(shù)情況下如果是基于Web API的架構(gòu)下,如果是基于數(shù)據(jù)庫表的處理,我覺得領域的業(yè)務管理類也是不必要的,直接使用倉儲的標準對象處理,已經(jīng)可以滿足大多數(shù)的需要了,一些邏輯我們可以在Application Service里面實現(xiàn)以下即可。

3)字典模塊業(yè)務類的簡化

我們以字典模塊的字典類型表來介紹。
領域業(yè)務對象接口層定義如下所示(類似IBLL)

    /// <summary>
    /// 領域業(yè)務管理接口
    /// </summary>
    public interface IDictTypeManager : IDomainService
    {
        /// <summary>
        /// 獲取所有字典類型的列表集合(Key為名稱,Value為ID值)
        /// </summary>
        /// <param name="dictTypeId">字典類型ID,為空則返回所有</param>
        /// <returns></returns>
        Task<Dictionary<string, string>> GetAllType(string dictTypeId);

    }

領域業(yè)務對象管理類(類似BLL)

    /// <summary>
    /// 領域業(yè)務管理類實現(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;
        }
    }

然后領域?qū)ο蟮膽梅諏咏涌趯崿F(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è)務邏輯類的處理,不過這樣的做法,對于常規(guī)數(shù)據(jù)庫的處理來說,顯得有點累贅,還需要多定義一個業(yè)務對象接口和一個業(yè)務對象實現(xiàn),同時在應用層接口里面,也需要多增加一個接口參數(shù),總體感覺有點多余,因此我把它改為使用標準的倉儲對象來處理就可以達到同樣的目的了。

在項目其中對應位置,刪除字典類型的一個業(yè)務對象接口和一個業(yè)務對象實現(xiàn),改為標準倉儲對象的接口處理,相當于把業(yè)務邏輯里面的代碼提出來放在服務層而已,那么在應用服務層的處理代碼如下所示。

    [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;
        }

......

這樣我們少定義兩個文件,以及減少協(xié)調(diào)業(yè)務類的代碼,代碼更加簡潔和容易理解,反正最終實現(xiàn)都是基于倉儲對象的接口調(diào)用。

另外,我們繼續(xù)了解項目,知道在Web.Host項目是我們Web API層啟動,且動態(tài)構(gòu)建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的管理界面如下所示。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內(nèi)容