簡體   English   中英

洋蔥架構,工作單元和通用存儲庫模式

[英]Onion Architecture, Unit of Work and a generic Repository pattern

這是我第一次實施更加以域驅動的設計方法。 我決定嘗試使用Onion Architecture,因為它專注於域而不是基礎架構/平台/等。

在此輸入圖像描述

為了從實體框架中抽象出來,我創建了一個帶有工作單元實現的通用存儲庫

IRepository<T>IUnitOfWork接口:

public interface IRepository<T>
{
    void Add(T item);

    void Remove(T item);

    IQueryable<T> Query();
}

public interface IUnitOfWork : IDisposable
{
    void SaveChanges();
}

IRepository<T>IUnitOfWork實體框架實現:

public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> dbSet;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        if (entityFrameworkUnitOfWork == null)
        {
            throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
        }

        dbSet = entityFrameworkUnitOfWork.GetDbSet<T>();
    }

    public void Add(T item)
    {
        dbSet.Add(item);
    }

    public void Remove(T item)
    {
        dbSet.Remove(item);
    }

    public IQueryable<T> Query()
    {
        return dbSet;
    }
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new CustomerContext();;
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

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

    public void Dispose()
    {
        context.Dispose();
    }
}

客戶存儲庫:

public interface ICustomerRepository : IRepository<Customer>
{

}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
    {
    }
}

使用存儲庫的ASP.NET MVC控制器:

public class CustomerController : Controller
{
    UnityContainer container = new UnityContainer();

    public ActionResult List()
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();

        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        var unitOfWork = container.Resolve<IUnitOfWork>();
        var customerRepository = container.Resolve<ICustomerRepository>();; 

        customerRepository.Add(customer);

        unitOfWork.SaveChanges();

        return RedirectToAction("List");
    }
}

統一依賴注入:

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();

解:

在此輸入圖像描述

問題?

  • 存儲庫實現(EF代碼)非常通用。 它都位於EntityFrameworkRepository<T>類的旁邊。 具體模型存儲庫不包含任何此邏輯。 這使我免於編寫大量冗余代碼,但可能會犧牲靈活性?

  • ICustomerRepositoryCustomerRepository類基本上是空的。 它們純粹是為了提供抽象。 據我所知,這符合洋蔥架構的願景,其中基礎架構和平台相關的代碼位於系統外部,但是空類和空接口感覺不對?

  • 要使用不同的持久性實現(例如Azure表存儲),則需要創建新的CustomerRepository類並繼承AzureTableStorageRepository<T> 但這可能導致冗余代碼(多個CustomerRepositories)? 這種效果會如何嘲弄?

  • 另一個實現(比如Azure表存儲)對跨國支持有限制,因此AzureTableStorageUnitOfWork類在此上下文中不起作用。

我這樣做的方式還有其他問題嗎?

(我從這篇文章中獲取了大部分靈感)

我可以說這個代碼第一次嘗試已經足夠好但是確實有一些地方需要改進。

讓我們來看看其中的一些。

1.依賴注入(DI)和IoC的使用。

您使用最簡單的Service Locator模式版本 - container實例本身。

我建議你使用'構造函數注入'。 您可以在此處找到更多信息(ASP.NET MVC 4依賴注入)

public class CustomerController : Controller
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ICustomerRepository customerRepository;

    public CustomerController(
        IUnitOfWork unitOfWork, 
        ICustomerRepository customerRepository)
    {
        this.unitOfWork = unitOfWork;
        this.customerRepository = customerRepository;
    }

    public ActionResult List()
    {
        return View(customerRepository.Query());
    }

    [HttpPost]
    public ActionResult Create(Customer customer)
    {
        customerRepository.Add(customer);
        unitOfWork.SaveChanges();
        return RedirectToAction("List");
    }
}

2.工作單位(UoW)范圍。

我找不到IUnitOfWorkICustomerRepository生活方式。 我不熟悉Unity,但msdn說默認使用TransientLifetimeManager 這意味着每次解析類型時都會獲得一個新實例。

因此,以下測試失敗:

[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
    target.RegisterType<ICustomerRepository, CustomerRepository>();

    //act
    var unitOfWork1 = target.Resolve<IUnitOfWork>();
    var unitOfWork2 = target.Resolve<IUnitOfWork>();

    // assert
    // This Assert fails!
    unitOfWork1.Should().Be(unitOfWork2);
} 

我期待的該實例UnitOfWork在控制器從實例不同UnitOfWork在你的倉庫。 有時可能會導致錯誤。 但它並沒有在ASP.NET MVC 4依賴注入中突出顯示為Unity的一個問題。

Castle Windsor中, PerWebRequest生活方式用於在單個http請求中共享相同的類型實例。

UnitOfWorkPerWebRequest組件時,這是常見的方法。 可以使用自定義ActionFilter ,以便在調用OnActionExecuted()方法期間調用Commit()

我也將重新命名SaveChanges()方法,並調用它只是Commit因為它是所謂的例子 ,並在POEAA

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

3.1。 對存儲庫的依賴性。

如果您的存儲庫將是“空的”,則不需要為它們創建特定的接口。 可以解析IRepository<Customer>並在控制器中包含以下代碼

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository)
{
    this.unitOfWork = unitOfWork;
    this.customerRepository = customerRepository;
}

有一個測試它的測試。

[Test]
public void MyTest()
{
    var target = new UnityContainer();
    target.RegisterType<IRepository<Customer>, CustomerRepository>();

    //act
    var repository = target.Resolve<IRepository<Customer>>();

    // assert
    repository.Should().NotBeNull();
    repository.Should().BeOfType<CustomerRepository>();
}

但是,如果您希望將存儲庫設置為“查詢構造代碼集中的映射層上的抽象層”。 PoEAA,存儲庫

存儲庫在域和數據映射層之間進行調解,其作用類似於內存中的域對象集合。 客戶端對象以聲明方式構造查詢規范,並將它們提交給Repository以滿足要求。

3.2。 EntityFrameworkRepository上的繼承。

在這種情況下,我將創建一個簡單的IRepository

public interface IRepository
{
    void Add(object item);

    void Remove(object item);

    IQueryable<T> Query<T>() where T : class;
}

及其實現,知道如何使用EntityFramework基礎結構,並可以很容易地被另一個(例如AzureTableStorageRepository )替換。

public class EntityFrameworkRepository : IRepository
{
    public readonly EntityFrameworkUnitOfWork unitOfWork;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        if (entityFrameworkUnitOfWork == null)
        {
            throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
        }

        this.unitOfWork = entityFrameworkUnitOfWork;
    }

    public void Add(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Add(item);
    }

    public void Remove(object item)
    {
        unitOfWork.GetDbSet(item.GetType()).Remove(item);
    }

    public IQueryable<T> Query<T>() where T : class
    {
        return unitOfWork.GetDbSet<T>();
    }
}

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new CustomerContext();
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

    internal DbSet GetDbSet(Type type)
    {
        return context.Set(type);
    }

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

    public void Dispose()
    {
        context.Dispose();
    }
}

現在CustomerRepository可以作為代理並引用它。

public interface IRepository<T> where T : class
{
    void Add(T item);

    void Remove(T item);
}

public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
    protected readonly IRepository Repository;

    protected RepositoryBase(IRepository repository)
    {
        Repository = repository;
    }

    public void Add(T item)
    {
        Repository.Add(item);
    }

    public void Remove(T item)
    {
        Repository.Remove(item);
    }
}

public interface ICustomerRepository : IRepository<Customer>
{
    IList<Customer> All();

    IList<Customer> FindByCriteria(Func<Customer, bool> criteria);
}

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository
{
    public CustomerRepository(IRepository repository)
        : base(repository)
    { }

    public IList<Customer> All()
    {
        return Repository.Query<Customer>().ToList();
    }

    public IList<Customer> FindByCriteria(Func<Customer, bool> criteria)
    {
        return Repository.Query<Customer>().Where(criteria).ToList();
    }
}

我唯一看到的是你高度依賴你的IOC工具,所以要確保你的實現是可靠的。 然而,這並非洋蔥設計所獨有。 我在一些項目中使用了Onion,並沒有碰到任何真正的“陷阱”。

我在代碼中看到了幾個嚴重的問題。

第一個問題是存儲庫和UoW之間的關系。

    var unitOfWork = container.Resolve<IUnitOfWork>();
    var customerRepository = container.Resolve<ICustomerRepository>();

這是隱式依賴。 沒有UoW,存儲庫將無法正常工作! 並非所有存儲庫都需要與UoW連接。 例如存儲過程怎么樣? 您有存儲過程,並將其隱藏在存儲庫后面。 存儲過程調用使用單獨的事務! 至少在所有情況下都不是。 因此,如果我解析唯一的存儲庫並添加項目,那么它將無法工作。 此外,如果我設置Transient life license,則此代碼將不起作用,因為存儲庫將具有另一個UoW實例。 所以我們有緊密的隱式耦合。

第二個問題是你在DI容器引擎之間建立緊密耦合並將其用作服務定位器! 服務定位器不是實現IoC和聚合的好方法。 在某些情況下,它是反模式。 應使用DI容器

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM