ABP開發框架先後端開發系列---(2)框架的初步介紹

在前面隨筆《ABP開發框架先後端開發系列---(1)框架的整體介紹》大概介紹了這個ABP框架的主要特色,以及介紹了我對這框架的Web API應用優先的一些見解,本篇繼續探討ABP框架的初步使用,也就是咱們下載到的ABP框架項目(基於ABP基礎項目的擴展項目),若是理解各個組件模塊,以及如何使用。html

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

整個基礎的ABP框架看似很是龐大,其實不少項目也不多內容,主要是獨立封裝不一樣的組件進行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等內容,基本上咱們主要關注的內容就是Abp這個主要的項目裏面,其餘的是針對不一樣的組件應用作的封裝。git

而基於基礎ABP框架擴展出來的ABP應用項目,則簡單不少,咱們也是在須要用到不一樣組件的時候,才考慮引入對應的基礎模塊進行使用,通常來講,主要仍是基於倉儲管理實現基於數據庫的應用,所以咱們主要對微軟的實體框架的相關內容瞭解清楚便可。github

這個項目是一個除了包含基礎的人員、角色、權限、認證、配置信息的基礎項目外,而若是你從這裏開始,對於其中的一些繼承關係的瞭解,會增長不少困難,由於它們基礎的用戶、角色等對象關係實在是很複雜。數據庫

我建議從一個簡單的項目開始,也就是基於一兩個特定的應用表開始的項目,所以能夠參考案例項目:eventcloud 或者 sample-blog-module 項目,咱們入門理解起來可能更加清楚。這裏我以eventcloud項目來進行分析項目中各個層的類之間的關係。後端

咱們先從一個關係圖來了解下框架下的領域驅動模塊中的各個類之間的關係。api

先以領域層,也就是項目中的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; }
     
        ......
   }

這個裏面定義了領域實體和表名之間的關係,其餘屬性也就是對應數據庫的字段了async

[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的管理界面以下所示。

 

相關文章
相關標籤/搜索