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

.net core webapi +ddd(领域驱动)+nlog配置+swagger配置 学习笔记(2)的更多相关文章

  1. .net core webapi +ddd(领域驱动)+nlog配置+swagger配置 学习笔记(1)

    搭建一个.net core webapi项目  在开始之前,请先安装最新版本的VS2017,以及最新的.net core 2.1. 首先创建一个Asp.Net Core Web应用程序 这个应用程序是 ...

  2. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  3. DDD 领域驱动设计-如何控制业务流程?

    上一篇:<DDD 领域驱动设计-如何完善 Domain Model(领域模型)?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sa ...

  4. C#进阶系列——DDD领域驱动设计初探(七):Web层的搭建

    前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码.之前的六篇完成了领域层.应用层.以及基础结构层的部分代码,这篇打算搭建下UI层的代码. DDD领域驱动设计初 ...

  5. DDD领域驱动设计:CQRS

    1 前置阅读 在阅读本文章之前,你可以先阅读: DDD领域驱动设计是什么 DDD领域驱动设计:实体.值对象.聚合根 DDD领域驱动设计:仓储 MediatR一个优秀的.NET中介者框架 2 什么是CQ ...

  6. DDD领域驱动设计-概述-Ⅰ

     如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.)         ...

  7. DDD领域驱动设计-项目包结构说明-Ⅳ

     基于DDD领域驱动设计的思想,在开发具体系统时,需要先建立不同的层级包.主要是梳理不同层面(应用层,领域层,基础设施层,展示层)包括的功能目录,每一个层面应该包括哪些模块.本例所讲述的分层是DDD落 ...

  8. DDD领域驱动设计-设计规范-Ⅵ

    不以规矩,不能成方圆.                                                                     -战国·邹·孟轲<孟子·离娄章句上 ...

  9. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

随机推荐

  1. 使用 sqoop 将mysql数据导入到hdfs(import)

    Sqoop 将mysql 数据导入到hdfs(import) 1.创建mysql表 CREATE TABLE `sqoop_test` ( `id` ) DEFAULT NULL, `name` va ...

  2. kreuz-frankfurt-sample-generic-2019-02-08.xodr文件解读

    第1行:xml语法所遵循的版本. L2:文件封装标记. L3:Opendrive的主要修订编号  次要修订编号   供应商. L4:记录有关地理参考坐标系的参数,投影-横轴墨卡托   a-地球椭球长半 ...

  3. 很实用的HTML5+CSS3注册登录窗体切换效果

    1. [代码]3个很实用的HTML5+CSS3注册登录窗体切换效果 <!DOCTYPE html><!--[if lt IE 7 ]> <html lang=" ...

  4. Linux学习过程中的简单命令

    1.su su- 与 sudo     (1) 普通用户和root转换:su 用户名或root              不知道root密码的情况下:普通 -> root:sudo su roo ...

  5. jenkins-小知识点

    如果想停止jenkins运行 控制面板-服务-查看本地服务-选中jenkins 1.启动类型改为手动 2.改为禁止 使用的时候,每次都改一下状态

  6. 素数环:NYOJ--488--dfs||hdu-1016-Prime Ring Problem

    /* Name: NYOJ--488--素数环 Author: shen_渊 Date: 15/04/17 15:30 Description: DFS,素数打个表,37以内就够用了 */ #incl ...

  7. L100

    The world’s lightest wireless flying machine lifts off1Circult: cutting the circuitry from copper fo ...

  8. 【leetcode刷题笔记】N-Queens II

    Follow up for N-Queens problem. Now, instead outputting board configurations, return the total numbe ...

  9. Java基础 之 System.getProperty()方法

    Java基础 之 System.getProperty()方法大全 public static void main(String[] args) { System.out.println(" ...

  10. 比线程更NB的存在

    阅读目录 一 引子 二 协程介绍 三 Greenlet模块 四 Gevent模块 引子 之前我们学习了线程.进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位.按道理来 ...