.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 就到这吧。

 

posted @ 2018-09-01 23:50  peigen  阅读(3389)  评论(3编辑  收藏  举报

目录导航