重新整理 .net core 實踐篇—————應用分層[二十四]
前言
簡單整理一下分層。
正文
應用程式分層,分為:
1.領域模型層
2.基礎設施層
3.應用層
4.共享層
共享層
共享層一般包括下面幾個類庫。
- 有一個Core 的類庫,比如說BLog.Core.
這個類庫用來,主要用來承載一些基礎簡單的型別,比如說一下幫助類。
- 共享層的抽象層。 比如說有一個Blog.Domain.Abstractions(Domain就是領域模型) 這樣一個抽象層。
這個抽象成用來在領域模型中定義一些基類或者介面或者領域事件的介面、領域事件處理的介面還有entity的介面和entity的基類。
領域模型中定義一些基類或者介面或者領域事件的介面,比如說:
/// <summary> /// 領域事件介面 /// 用來標記我們某一個物件是否是領域事件 /// </summary> public interface IDomainEvent : INotification { }
/// <summary> /// 領域事件處理器介面 /// </summary> public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent { //這裡我們使用了INotificationHandler的Handle方法來作為處理方法的定義,所以無需重新定義 //Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken); }
/// <summary> /// 值物件 /// TODO 領域驅動中比較關鍵的類 /// </summary> public abstract class ValueObject { protected static bool EqualOperator(ValueObject left, ValueObject right) { if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) { return false; } return ReferenceEquals(left, null) || left.Equals(right); } protected static bool NotEqualQperator(ValueObject left, ValueObject right) { return !(EqualOperator(left, right)); } /// <summary> /// 獲取值物件原子值(欄位值) /// 將我們值物件的欄位輸出出來,作為唯一標識來判斷我們兩個物件是否想等 /// </summary> /// <returns></returns> protected abstract IEnumerable<object> GetAtomicValues(); public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) { return false; } ValueObject other = (ValueObject)obj; IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator(); IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator(); while (thisValues.MoveNext() && otherValues.MoveNext()) { if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null)) { return false; } if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current)) { return false; } } return !thisValues.MoveNext() && !otherValues.MoveNext(); } public override int GetHashCode() { return GetAtomicValues() .Select(x => x != null ? x.GetHashCode() : 0) .Aggregate((x, y) => x ^ y); } }
這種驅動領域的一些處理抽象。
- 基礎設施的核心層,比如說可以取名:Blog.Infrastructure.core
是指我們可以對倉儲還有EFContext定義一些共享程式碼。
這個共享層,一般會單獨打包成一個dll,然後會放在公司的nuget管理裡面。
比如說:泛型倉儲介面
/// <summary>
/// 泛型倉儲介面
/// </summary>
/// <typeparam name="TEntity">實體型別</typeparam>
public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot
{
IUnitOfWork UnitOfWork { get; }
TEntity Add(TEntity entity);
Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
// 當前介面未指定主鍵型別,所以這裡需要根據實體物件去刪除
bool Remove(Entity entity);
Task<bool> RemoveAsync(Entity entity);
}
/// <summary>
/// 泛型倉儲介面
/// </summary>
/// <typeparam name="TEntity">實體型別</typeparam>
/// <typeparam name="TKey">主鍵Id型別</typeparam>
public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot
{
bool Delete(TKey id);
Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default);
TEntity Get(TKey id);
Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default);
}
EF上下文:
/// <summary>
/// EF上下文
/// 注:在處理事務的邏輯部分,需要嵌入CAP的程式碼,建構函式引數 ICapPublisher
/// </summary>
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
protected IMediator _mediator;
ICapPublisher _capBus;
public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus)
: base(options)
{
_mediator = mediator;
_capBus = capBus;
}
#region IUnitOfWork
/// <summary>
/// 儲存實體變更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
// 執行傳送領域事件
await _mediator.DispatchDomainEventsAsync(this);
return true;
}
///// <summary>
///// IUniOfWork中該方法的定義與DbContext中的SaveChangesAsync一致,所以此處無需再進行實現
///// </summary>
///// <param name="cancellationToken"></param>
///// <returns></returns>
//public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
//{
// return base.SaveChangesAsync();
//}
#endregion
#region ITransaction
/// <summary>
/// 當前事務
/// </summary>
private IDbContextTransaction _currentTransaction;
/// <summary>
/// 公開方法,返回當前私有事務物件
/// </summary>
/// <returns></returns>
public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
/// <summary>
/// 當前事務是否開啟
/// </summary>
public bool HasActiveTransaction => _currentTransaction == null;
/// <summary>
/// 開啟事務
/// </summary>
/// <returns></returns>
public Task<IDbContextTransaction> BeginTransactionAsync()
{
if (_currentTransaction != null)
{
return null;
}
// 該擴充套件方法是由CAP元件提供
// 建立事務時,也要把 ICapPublisher 傳入
// 核心作用是將我們要傳送事件邏輯與我們業務的儲存都放在同一個事務內部,從而保證事件與業務邏輯的存取都是一致的
_currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
return Task.FromResult(_currentTransaction);
}
/// <summary>
/// 提交事務
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null)
{
throw new ArgumentNullException(nameof(transaction));
}
if (transaction != _currentTransaction)
{
throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");
}
try
{
// 提交事務之前,安全起見還是要 SaveChanges 一下,儲存變更到資料庫
await SaveChangesAsync();
transaction.Commit();
}
catch (Exception ex)
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
/// <summary>
/// 回滾事務
/// </summary>
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
#endregion
}
Domain 層(領域模型)
裡面就是一些領域事件,領域結構。
基礎設施層
一般用來定義了倉儲層的實現
應用層
就是我們的api介面層了還有就是我們的一些job任務類庫。
這裡的Application如果是大一點的專案其實是會獨立出去的。
可以叫做:Blog.Application這樣子。裡面可以存放一些服務或者領域模型的命令和查詢等。
儘量讓應用層只做和客戶端的互動,如果需要其他服務,在其他類庫中呼叫即可。
梳理
上面看這些可能有點亂,這裡來梳理一下。
什麼是共享層。 以前呢,我們會有一個common 類庫,專門用來存放共享類的,比如一些helper類了。
但是呢,人們發現一個問題,因為common類庫呢,會引用很多包,就有點亂了。
第二個呢,common包裡面的helper越來越多,越來越難管理,找起來也麻煩。
然後很多東西就獨立出來了,比如說基礎設施類庫,這個類庫用來做一些基礎設施的。
那麼什麼是基礎設施呢?就是說如果這個專案沒有什麼是跑不起來的,就比如說這個專案要和資料庫打交道,那麼資料庫就是基礎設施了。
然後呢,領域驅動又屬於一個獨立的東西,那麼又可以分為一個類庫了。
總的來說是對我們的一個common類庫進行梳理。
然後來說一下基礎設施層和領域驅動層,這些就是該專案的實現,需要什麼功能那麼寫什麼樣的介面。
應用層就是和客戶端打交道的層。
應用層不是說只是一個應用api,比如說blog.api這樣的。
它可以獨立出去很多類庫,比如說blog.aplication(一些服務或者一些具體業務)或者blog.BackgroundTasks(一些後臺服務)讓blog.api只專注於和客戶端打交道的。
總結
-
領域模型專注於業務的設計,不依賴倉儲等基礎設施層。
-
基礎設施的倉儲層僅負責領域模型的取出和儲存
-
使用CQRS(查詢與命令分開)模型設計引用層。
-
Web Api 是面向前端互動的介面,避免依賴領域模型
-
將共享程式碼設計為共享包,使用私有Nuget 倉庫分發管理
結
下一節領域模型內在邏輯和外在行為。