DDD領域驅動設計:倉儲


1 前置閱讀

在閱讀本文章之前,你可以先閱讀:

  • 什么是DDD
  • DDD的實體、值對象、聚合根的基類和接口:設計與實現

2 什么是倉儲?

倉儲封裝了基礎設施來提供查詢和持久化聚合操作。 它們集中提供常見的數據訪問功能,從而提供更好的可維護性,並將用於訪問數據庫的基礎結構或技術與領域模型層分離。 創建數據訪問層和應用程序的業務邏輯層之間的抽象層。 實現倉儲可讓你的應用程序對數據存儲介質的更改不敏感。

3 為什么倉儲?

直接訪問數據:

  • 重復的代碼
  • 難以集中化與數據相關的策略(例如緩存)
  • 編程錯誤的可能性更高
  • 無法獨立於外部依賴項輕松測試業務邏輯

使用倉儲優點:

  • 可以通過將業務邏輯與數據或服務訪問邏輯分開來提高代碼的可維護性和可讀性。
  • 可以從許多位置訪問數據源,並希望應用集中管理的,一致的訪問規則和邏輯。
  • 可以通過自動化進行測試的代碼量,並隔離數據層以支持單元測試。
  • 可以使用強類型的業務實體,以便可以在編譯時而不是在運行時識別問題。
  • 可以將行為與相關數據相關聯。例如,您要計算字段或在實體中的數據元素之間強制執行復雜的關系或業務規則。
  • 應用DDD來簡化復雜的業務邏輯。

4 實現倉儲?

實現基本的增刪改查及事務的提交和回滾

首先,定義接口

/// <summary>
/// IRepository提供應用程序倉儲模式基本操作的接口
/// </summary>
public interface IRepository
{
    #region Methods
    void Entry<T>(T t) where T : AggregateRoot;
    void Save();
    T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
    T Get<T>(Expression<Func<T, bool>> where, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
    IQueryable<T> Query<T>(Expression<Func<T, bool>> filter=null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy=null, Func<IQueryable<T>, IQueryable<T>> includes=null) where T : AggregateRoot;
    IQueryable<T> QueryByPage<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot;
    void BeginTransaction();
    void Commit();
    void Rollback();
    #endregion
}

最后,實現以上接口

public class Repository : IDisposable, IRepository
{
    #region Private Fields
    private readonly DbContext context;
    private IDbContextTransaction transaction;
    #endregion

    #region Constructors
    public Repository(DbContext context)
    {
        this.context = context ?? throw new ArgumentNullException(nameof(context));
    }
    #endregion

    #region IRepository<T> Members
    public void Entry<T>(T t) where T : AggregateRoot
    {
        switch (t.AggregateState)
        {
            case AggregateState.Added:
                context.Entry(t).State = EntityState.Added;
                break;
            case AggregateState.Deleted:
                context.Entry(t).State = EntityState.Deleted;
                break;
            default:
                context.Entry(t).State = EntityState.Modified;
                break;
        }
    }

    public void Save()
    {
        context.SaveChanges();
    }

    public T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
    {
        return Get(w => w.Id.Equals(id), includes: includes);
    }

    public T Get<T>(Expression<Func<T, bool>> filter, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
    {
        return Query(filter, includes: includes).SingleOrDefault();
    }

    public IQueryable<T> Query<T>(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
    {
        IQueryable<T> query = context.Set<T>();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (includes != null)
        {
            query = includes(query);
        }

        if (orderBy != null)
        {
            query = orderBy(query);
        }

        return query;
    }

    public IQueryable<T> QueryByPage<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
    {
        var query = Query(filter, orderBy, includes)
            .Skip(pageSize * (pageIndex - 1))
            .Take(pageSize);

        return query;
    }

    public void BeginTransaction()
    {
        transaction = context.Database.BeginTransaction();
    }

    public void Rollback()
    {
        transaction.Rollback();
    }

    public void Commit()
    {
        transaction.Commit();
    }

    public void Dispose()
    {
        if (transaction != null)
        {
            transaction.Dispose();
        }
        context.Dispose();
    }
    #endregion
}

為數據庫上下文和事務上下文聲明類變量:

private readonly DbContext context;
private IDbContextTransaction transaction;

構造函數接受數據庫上下文實例:

public Repository(DbContext context)
{
    this.context = context ?? throw new ArgumentNullException(nameof(context));
}

Get分為通過ID查詢或過濾條件進行查詢,返回序列中的唯一元素:

public T Get<T>(Guid id, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
    return Get(w => w.Id.Equals(id), includes: includes);
}
public T Get<T>(Expression<Func<T, bool>> filter, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
    return Query(filter, includes: includes).SingleOrDefault();
}

Query 方法使用 lambda 表達式來允許調用代碼指定篩選條件,使用一列來對結果進行排序,允許調用方為預先加載導航屬性列表:

// 代碼 Expression<Func<T, bool>> filter 意味着調用方將基於 AggregateRoot 類型提供 lambda 表達式,並且此表達式將返回一個布爾值。
// 代碼 Func<IQueryable<T>, IOrderedQueryable<T>> orderBy 也意味着調用方將提供 lambda 表達式。 但在這種情況下,表達式的輸入是 AggregateRoot 類型的 IQueryable 對象。 表達式將返回 IQueryable 對象的有序版本。 
// 代碼 Func<IQueryable<T>, IQueryable<T>> includes 也意味着調用方將提供 lambda 表達式。 允許預先加載導航屬性列表。 

public IQueryable<T> Query<T>(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Func<IQueryable<T>, IQueryable<T>> includes = null) where T : AggregateRoot
{
    IQueryable<T> query = context.Set<T>();

    if (filter != null)
    {
        query = query.Where(filter);
    }

    if (includes != null)
    {
        query = includes(query);
    }

    if (orderBy != null)
    {
        query = orderBy(query);
    }

    return query;
}

Entry 方法使用 AggregateRoot.AggregateState 來置 context.Entry(t).State 狀態,完成增刪改

public void Entry<T>(T t) where T : AggregateRoot
{
    switch (t.AggregateState)
    {
        case AggregateState.Added:
            context.Entry(t).State = EntityState.Added;
            break;
        case AggregateState.Deleted:
            context.Entry(t).State = EntityState.Deleted;
            break;
        default:
            context.Entry(t).State = EntityState.Modified;
            break;
    }
}

Save 等其他方法也類似實現。


免責聲明!

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



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