.net core webapi +ddd(領域驅動)+nlog配置+swagger配置 學習筆記(2)


DDD領域驅動模型設計

什么是DDD

軟件開發不是一蹴而就的事情,我們不可能在不了解產品(或行業領域)的前提下進行軟件開發,在開發前,通常需要進行大量的業務知識梳理,而后到達軟件設計的層面,最后才是開發。而在業務知識梳理的過程中,我們必然會形成某個領域知識,根據領域知識來一步步驅動軟件設計,就是領域驅動設計的基本概念。

聽起來這和傳統意義的軟件開發沒啥區別,只是換了點新鮮的名詞而已,其實不然。

 

該架構分成了Interfaces、Applications和Domain三層以及包含各類基礎設施的Infrastructure。下圖簡略描述了它們之間的關系:

圖1:領域驅動設計風格的架構草圖(來自於DDDSample官網) 

 

下圖是詳細架構:

圖2:領域驅動設計參考架構 

 

  • Interface

負責向用戶展現信息,並且會解析用戶行為,即常說的展現層。

  • Application 

應用層沒有任何的業務邏輯代碼,它很簡單,它主要為程序提供任務處理。

  • Domain

這一層包含有關領域的信息,是業務的核心,領域模型的狀態都直接或間接(持久化至數據庫)存儲在這一層。 

  • Infrastructure

為其他層提供底層依賴操作。

 

層結構的划分是很有必要的,只有清晰的結構,那么最終的領域設計才宜用,比如用戶要預定航班,向Application的service發起請求,而后Domain從Infrastructure獲取領域對象,校驗通過后會更新用戶狀態,最后再次通過Infratructure持久化到數據庫中。

 

那么根據這些我們就可以設計出自己得項目。當然需要靈活運用。

在此之前 我們先添加一些基礎類庫,然后分開層次。

目前我得項目大體是這樣分層得。至於列出12345 是為了更加整齊,做程序員別的事情可以拖沓,但是寫代碼拖沓得真的不是一個好習慣。

 

Domain里面 我放入兩個2文件夾,其中Entity是數據實體類,IOModel意思得Input  Output得意思,專門處理傳入傳出得實體類。

 Entity中 可以寫入一些基類,比如我得

     /// <summary>
    /// 實體標准基類
    /// </summary>
    public abstract class StandardBaseEntity : ReadonlyBaseEntity
    {
        protected StandardBaseEntity(int userId):base(userId)
        {
            SetAddUserIdAndTime(userId);
        }

        
        /// <summary>
        /// 最后更新操作人ID
        /// </summary>
        public int LastUpdateUserId { get; set; }

        /// <summary>
        /// 最后更新時間
        /// </summary>
        public DateTime LastUpdateTime { get; set; }

        /// <summary>
        /// 行版本 (時間戳處理並發)
        /// </summary>
        public byte[] DataTimestamp { get; set; }

        /// <summary>
        /// 填寫添加時的標准信息
        /// </summary>
        /// <param name="userId"></param>
        public new void SetAddUserIdAndTime(int userId)
        {
            base.SetAddUserIdAndTime(userId);
            LastUpdateUserId = userId;
            LastUpdateTime = DateTime.Now;
        }
        /// <summary>
        /// 填寫更新時的標准信息
        /// </summary>
        /// <param name="userId"></param>
        public void SetUpdateUserIdAndTime(int userId)
        {
            LastUpdateUserId = userId;
            LastUpdateTime = DateTime.Now;
        }
    }

  

    /// <summary>
    /// 實體簡化基類
    /// </summary>
    public abstract class ReadonlyBaseEntity
    {
        protected ReadonlyBaseEntity() { }

        protected ReadonlyBaseEntity(int userId)
        {
            SetAddUserIdAndTime(userId);
        }

        public virtual MethodResultFull<bool> Validate()
        {
            if (_validator == null)
            {
                throw new NullReferenceException(nameof(_validator));
            }
            ValidationResult validateResult = _validator.Validate(this).FirstOrDefault();
            MethodResultFull<bool> result = new MethodResultFull<bool>();
            if (validateResult == null)
            {
                result.Content = true;
            }
            else
            {
                result.ResultNo = validateResult.ErrorMessage;
            }

            return result;
        }

        
        /// <summary>
        /// 創建操作人ID
        /// </summary>
        public int CreateUserId { get; set; }
        /// <summary>
        /// 創建時間
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 填寫添加時的標准信息
        /// </summary>
        /// <param name="userId"></param>
        public void SetAddUserIdAndTime(int userId)
        {
            CreateUserId = userId;
            CreateTime = DateTime.Now;
        }


        private static IValidator _validator = new DataAnnotationsValidator();

        public static void SetValidator(IValidator valiator)
        {
            _validator = valiator;
        }

  這個看自己得需求,我得基類主要要處理一些公用得字段,公用方法,對實體類得某些字段加入自定義特性得驗證規則驗證。特性真得是一個非常有用得東西,至於怎么使用,自己去翻資料。

 

 

這個是Repository類內得一些文件。因為使用得是EntityFrameworkCore ,所以需要添加nugut引用。

 

 

 這里面需要注意得有幾塊

  • 第一    DataBaseContext,主要是數據庫鏈接,這個就需要前面在webapi中依賴注入。

 

 appsettings.json 添加數據庫連接

 

 

  • 第二    DatabaseFactory  和 UnitOfWork,為什么需要這個,其中DatabaseFactory是一個 DataBaseContext 簡單工廠,為 UnitOfWork 和 Repository 提供 DataBaseContext 上下文,其中UnitOfWork是工作單元主要處理,對數據最后得操作,這個是很有必要得。 使每一個HTTP請求只用一個DataBaseContext上下文,不需要重復的打開數據庫連接,減輕數據庫壓力

           在DataBaseContext 中 跟之前還有點區別得。

    public interface IDatabaseFactory
    {
        DataBaseContext Get();
    }


    /// <summary>
    /// 主要用於同一個DataBaseContext 上下文
    /// </summary>
    public class DatabaseFactory: Disposable, IDatabaseFactory
    {
        private DataBaseContext dataContext;
        private static readonly string connection = ConfigurationManager.AppSettings["SqlConnection"];
        private static readonly DbContextOptions<DataBaseContext> dbContextOption = new DbContextOptions<DataBaseContext>();
        private static readonly DbContextOptionsBuilder<DataBaseContext> dbContextOptionBuilder = new DbContextOptionsBuilder<DataBaseContext>(dbContextOption);
        public DataBaseContext Get()
        {
            return dataContext ?? (dataContext = new DataBaseContext(dbContextOptionBuilder.UseSqlServer(connection).Options));
        }

        protected override void DisposeCore()
        {
            if (dataContext != null)
                dataContext.Dispose();
        }
    }

  因為dataContext 沒有得話就需要New 一個新得,所以寫得稍微有點復雜。沒想到更好得解決辦法。有好得解決辦法希望提出來

 

IIRepository 代碼如下,基本上夠用了。

    public interface IRepository<T> where T : class
    { 
        //增
        void Add(T entity);
        void AddAll(IEnumerable<T> entities);
        //改
        void Update(T entity);
        void Update(IEnumerable<T> entities);
        //刪
        void Delete(T entity);
        void Delete(Expression<Func<T, bool>> where);
        void DeleteAll(IEnumerable<T> entities);

        void Clear();
        //查
        T GetById(long Id);
        T GetById(string Id);
        T Get(Expression<Func<T, bool>> where);
        IEnumerable<T> GetAll();
        IQueryable<T> GetMany(Expression<Func<T, bool>> where);
        IQueryable<T> GetAllLazy();
        DbSet<T> GetDbLazy();

       
    }

  

    public abstract class RepositoryBase<T> where T:class
    {
        private DataBaseContext dataContext;
        private readonly DbSet<T> dbset;
      
        protected IDatabaseFactory DatabaseFactory
        {
            get;
            private set;
        }
        protected DataBaseContext DataContext
        {
            get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
        }

        protected RepositoryBase(IDatabaseFactory databaseFactory)
        {
            DatabaseFactory = databaseFactory;
            dbset = DataContext.Set<T>();
        }

        #region 增刪查改
        /// <summary>
        /// 添加單條記錄
        /// </summary>
        /// <param name="entity">實體類</param>
        public void Add(T entity)
        {
            dbset.Add(entity);
        }
        /// <summary>
        /// 添加多條
        /// </summary>
        /// <param name="entities"></param>
        public virtual void AddAll(IEnumerable<T> entities)
        {
            dbset.AddRange(entities);
        }
        /// <summary>
        /// 更新一條
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(T entity)
        {
            //Attach要附加的實體。
            dbset.Attach(entity);
            DataContext.Entry(entity).State = EntityState.Modified;
        }
        /// <summary>
        /// 更新多條
        /// </summary>
        /// <param name="entities"></param>
        public virtual void Update(IEnumerable<T> entities)
        {
            foreach (var item in entities)
            {
                dbset.Attach(item);
                DataContext.Entry(item).State = EntityState.Modified;
            }
        }
        /// <summary>
        /// 刪除單條
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Delete(T entity)
        {
            dbset.Remove(entity);
        }
        /// <summary>
        /// 按條件刪除
        /// </summary>
        /// <param name="where"></param>
        public virtual void Delete(Expression<Func<T, bool>> where)
        {
            IEnumerable<T> objects = dbset.Where<T>(where).AsEnumerable();
            dbset.RemoveRange(objects);
        }
        /// <summary>
        /// 刪除多條
        /// </summary>
        /// <param name="entities"></param>
        public virtual void DeleteAll(IEnumerable<T> entities)
        {
            dbset.RemoveRange(entities);
        }
        public virtual void Clear()
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// 根據Id得到實體
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual T GetById(long id)
        {
            return dbset.Find(id);
        }
        /// <summary>
        /// 根據Id得到實體
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual T GetById(string id)
        {
            return dbset.Find(id);
        }
        /// <summary>
        /// 得到所有實體
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<T> GetAll()
        {
            return dbset.ToList();
        }
        /// <summary>
        /// 按條件得到多條實體
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public virtual IQueryable<T> GetMany(Expression<Func<T, bool>> where)
        {
            return dbset.Where(where);
        }
        /// <summary>
        /// 按條件得到單條實體
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public T Get(Expression<Func<T, bool>> where)
        {
            return dbset.Where(where).FirstOrDefault<T>();
        }

        public virtual IQueryable<T> GetAllLazy()
        {
            return dbset;
        }

        public virtual DbSet<T> GetDbLazy()
        {
            return dbset;
        }       
        #endregion
    }

  UnitOfWork代碼如下

    public interface IUnitOfWork
    {
        void Commit();
        void CommitAsync();
        IEnumerable<T> ExecuteQuery<T>(string sqlQuery, params object[] parameters) where T : class;
        int ExecuteCommand(string sqlCommand, params object[] parameters);
    }

  

    public class UnitOfWork : Disposable, IUnitOfWork
    {
        private DataBaseContext dataContext;
        protected IDatabaseFactory DatabaseFactory
        {
            get;
            private set;
        }
        protected DataBaseContext DataContext
        {
            get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
        }
        public UnitOfWork(IDatabaseFactory databaseFactory)
        {
            DatabaseFactory = databaseFactory;
        }
        /// <summary>
        /// 同步完成
        /// </summary>
        public void Commit()
        {
            DataContext.SaveChanges();
        }
        /// <summary>
        /// 異步完成
        /// </summary>
        public void CommitAsync()
        {
            DataContext.SaveChangesAsync();
        }
        /// <summary>
        /// 執行Sql 返回實體
        /// </summary>
        /// <param name="sqlQuery"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public virtual IEnumerable<T> ExecuteQuery<T>(string sqlQuery, params object[] parameters) where T:class
        {
            return DataContext.Set<T>().FromSql(sqlQuery, parameters);
        }
        /// <summary>
        /// 執行Sql 返回執行個數
        /// </summary>
        /// <param name="sqlCommand"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public virtual int ExecuteCommand(string sqlCommand, params object[] parameters)
        {
            return DataContext.Database.ExecuteSqlCommand(sqlCommand, parameters);
        }
        protected override void DisposeCore()
        {
            if (dataContext != null)
                dataContext.Dispose();
        }
    }

  其中 ExecuteQuery  ExecuteCommand是對Reposotry得補充,EF因為體量大,所以有些地方需要手寫Sql語句。可以看出,對於Repository里面得數據CURD,最終處理結果都交給工作單元來實現,Commit方法。一個事務中只需調用一次。

 

EFCore得映射也有變化,以前是直接引用基類EntityTypeConfiguration 就可以了,現在是手動實現,代碼如下。

public interface IEntityMappingConfiguration
    {
        void Map(ModelBuilder b);
    }

    public interface IEntityMappingConfiguration<T> : IEntityMappingConfiguration where T : class
    {
        void Map(EntityTypeBuilder<T> builder);
    }

    public abstract class EntityMappingConfiguration<T> : IEntityMappingConfiguration<T> where T : class
    {
        public abstract void Map(EntityTypeBuilder<T> b);

        public void Map(ModelBuilder b)
        {
            Map(b.Entity<T>());
        }
    }

    public static class ModelBuilderExtenions
    {
        private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
        {
            return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
        }

        public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
        {
            var mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>));

            var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
                                           .Where(type => !string.IsNullOrEmpty(type.Namespace))
                                           .Where(type => type.BaseType != null && type.BaseType.IsGenericType && (
                                           type.BaseType.GetGenericTypeDefinition() == typeof(ReadonlyBaseMap<>) ||
                                           type.BaseType.GetGenericTypeDefinition() == typeof(IEntityMappingConfiguration<>) ||
                                           type.BaseType.GetGenericTypeDefinition() == typeof(StandardBaseMap<>)) && type.Name != "StandardBaseMap`1" && type.Name != "ReadonlyBaseMap`1");
            foreach (var config in typesToRegister.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
            {
                config.Map(modelBuilder);
            }
        }
    }

  在ReadonlyBaseMap中 引用基類

 public class ReadonlyBaseMap<T> : EntityMappingConfiguration<T> where T : ReadonlyBaseEntity
    {
        public override void Map(EntityTypeBuilder<T> builder)
        {
            builder.Property(e => e.CreateTime).IsRequired();
        }
    }

   在StandardBaseMap 如下

 public class StandardBaseMap<T> : ReadonlyBaseMap<T> where T : StandardBaseEntity
    {
        public override void Map(EntityTypeBuilder<T> builder)
        {
            builder.Property(e => e.CreateUserId).IsRequired();
            builder.Property(e => e.LastUpdateUserId).IsRequired();
            builder.Property(e => e.LastUpdateTime).IsRequired();
            builder.Property(e => e.DataTimestamp).IsRowVersion();
        }
    }

  這樣我們就可以用一個方法處理映射關系,不需要重復添加各個實體類得映射 在DataBaseContext

 

 之后就是對外得Application了,這個就是一個IService 和 Service 其中注入IRepostory 實現業務邏輯。

 

當然其中少不了 依賴注入這個了,雖然.net core 提供了內置依賴注入方式。但是我用得是第三方Autofac,一個比較成熟的插件。

 

 

 

 

 

DDD 就到這吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM