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