DDD:存储库,工作单元,ORM和依赖注入

时间:2014-09-03 15:52:04

标签: c# entity-framework nhibernate dependency-injection domain-driven-design

修改

如果你在一个房间里放置10位域名专家,会发生什么?对,你有11个意见。 (其中10个被宣布为反模式

感谢大家的详细解答。我会研究它们并考虑它们如何帮助我解决特定问题。


我很难理解存储库和存储库。与ORM和依赖注入一起使用时的工作单元。考虑以下非常标准的接口:

public interface IRepository<TAggregateRoot> : ITransientDependency
{

    void Add(TAggregateRoot aggregateRoot);
    void Delete(TAggregateRoot aggregateRoot);
    IEnumerable<TAggregateRoot> GetAll();
}

public interface IUnitOfWork : ITransientDependency
{
    void Commit();
    void Rollback();
}

我脑子里有几个场景,我想用这些方法来解决。

  • 将单个实体插入存储库
  • 删除应删除链接实体的聚合
  • 在两个以上的存储库上执行交易

使用NHibernate的默认实现可能如下所示:

public abstract class NHibernateRepository<TAggregateRoot> : IRepository<TAggregate>
{
    protected NHibernateRepository(ISession session) {}
}

public sealed NHibernateUnitOfWork : IUnitOfWork
{
    public NHibernateUnitOfWork(ISession session)
    {}

    public void Commit() {
        _session.Flush();
    }
}

1.场景:将单个实体插入存储库

// ASP.NET MVC controller, but valid for any
// other arbitary application service
public class MyController : Controller {

    private readonly IPeopleRepository _repository;

    // di -> declaring IPeopleRepository dependency
    public MyController(IPeopleRepository repository) {
        _repository = repository;
    }

    public void AddPerson(Person person) {
        _repository.Add(person);
    }

}

现在,将我添加到存储库后会发生什么?没错,没事。即使单个插入不完全是工作单元(事务,技术),像EF和NHibernate这样的ORM框架仍然需要提交对数据库的更改,因为他们喜欢的会话 DBContexts 在技术上是工作单元存储库

我如何克服这个第一个问题?为我所做的一切开始工作单元

2.Scenario:删除也应删除链接实体的聚合

查看以下汇总:

public class Person : IAggregateRoot {

    private readonly List<Cat> _cats = new List<Cat>();

    public IEnumerable<Cat> Cats {
        get { return _cats; }
    }

    public void AddCat(Cat cat) {
    //

}

让我们通过存储库使用它的root删除聚合:

IPersonRepository.Remove(person);

现在, Person 聚合的所有实体都在技术上被删除。由于代码中不再有对它们的引用,因此垃圾收集器充当数据库管理器并从内存中删除 Cats

但是在 ORM 存储库实现中这可能是什么样子? 工作单元在哪里发挥作用?

3.Scenario:在两个以上的存储库上执行事务

好的,我在这里看到了SomethingService。他必须在多个存储库上做一些事情,因此显然需要一个事务来调用工作单元

public SomethingService : ISomethingService {

    public ISomethingService(IFirstRepo repo1, ISecondRepo repo2, IUnitOfWork uow)
    {
    ...
    }

    public void DoSomething() {
        repo1.AddThis();
        repo2.GetThisOne();
        repo2.BecauseOfTheOneAboveDeleteThis();
        uow.Commit();
    }

}

对我来说很好,但考虑到上面的 NHibernate 存储库和工作单元实现,这不会起作用,只是因为每个(工作单元) ,2个存储库)具有 NHibernate 会话的不同实例!

我使用拦截器考虑了面向方面的编程,但这仅部分起作用,因为 IoC 拦截服务方法的时间已经使用自己的会话创建了存储库,因此无法分享工作单元的会话。

如何克服这个问题?是否有任何完整的工作示例在没有任何肮脏黑客的情况下运行? (例如,单身作品单元)

只是说:是的,我想使用存储库和ORM。它们是抽象框架的好方法,让我按照我(或我的客户)想要的方式设计我的域名,而不是像框架想要的那样。

非常感谢你阅读这段文字。

4 个答案:

答案 0 :(得分:1)

情景1:

假设您的控制器托管在Web应用程序中,并且只有一个 Web请求应该导致实体插入存储库。诀窍是 然后对齐IUnitOfWork,以便在开始处理时创建它 处理完请求后请求并提交。

我不确定您正在使用哪个DI框架,但Unity有一个优雅的解决方案 ASP.NET MVC的形式为PerRequestLifetimeManager。所有类型注册使用 此生命周期管理器仅在单个Web请求的范围内缓存,并且 Web请求结束时自动处理。因此,如果你使用这一生 经理并使您的工作单位实施IDisposable,以便其提交 处置,你已经得到了照顾。

当发生错误时,你可能不得不做一点诡计, 虽然 - 可能在NHibernateUnitOfWork(见下文)。

情景2:

我会说IPersonRepository.Remove(person)应该知道明确删除所有内容 猫也是。这意味着您不能使用通用存储库,这是完美的 很好,因为它被认为(至少有些人)有点anti-pattern

场景3:

同样,解决方案是使用正确的生命周期管理器。各原因 存储库现在获得自己的ISession版本是因为DI容器考虑 ISession瞬态类型。如果你使用像PerResolve这样的东西,或者更好的话 再次PerRequest生命周期管理器,它将为每个实例重用相同的ISession实例 你的知识库。

顺便说一句,我注意到您的NHibernateRepository直接取决于ISessionNHibernateUnitOfWork - 有具体原因吗?我想我会的 让NHibernateUnitOfWork公开ISession属性(或者甚至可以重新公开其所有成员),并让所有存储库依赖于NHibernateUnitOfWork。 首先,因为存储库不只是“用会话做某事”,它们就是 实际上你正在做的工作单元 part 。另一方面,它更容易 使用NHibernateUnitOfWork作为外观,您可以在其中阻止实际的NHibernate会话 如果出现错误或未进行任何修改则提交。

在这种情况下,我会让NHibernateUnitOfWork使用PerRequest生命周期 比ISession

答案 1 :(得分:1)

一般来说,您可以通过将工作单元的控制权交给一个知道应用程序当前执行上下文的对象(在您的情况下为Controller)来解决您的问题。

工作单元是您的业务/应用程序事务,较低级别的持久性对象(如存储库)不了解总体上下文,也不应该决定事务何时完成。实际上,Repos应该只有一个UoW的引用,以便能够添加/删除它,但不能结束它。

关于DBContext和工作单元的细微之处,我已经非常好地解释了blog post。它是关于Entity Framework的DBContext的,但您可以根据NHibernate会话轻松翻译它。

  

您的服务必须是您申请中的唯一组成部分   负责在最后调用DbContext.SaveChanges()方法   商业交易应用程序的其他部分是否应该调用   SaveChanges()方法(例如存储库方法),你将结束   部分提交的更改,使您的数据不一致   状态。

对于场景3,您应该尽可能避免跨多个聚合的业务事务。将您的聚合精确设计为transactional consistency boundaries可能是个好主意。

如果您仍然需要因另一个聚合更改而影响一个聚合,最终的一致性可以得到拯救。例如,可以通过让第一个Aggregate发出域事件,然后事件处理程序(同步或异步)获取它并在单独的UoW中调用第二个Aggregate来实现。

答案 2 :(得分:1)

<强> 1。工作单元处理可以有所不同,


根据你的建议,会话被注入到存储库中,我不会继续这条路径,而是使用sessionFactory

public interface IUnitOfWork : IDisposable
{
    ISession CurrentSession { get; }
    void Commit();
    void Rollback();
}

public class NHibernateUnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory _sessionFactory;

    [ThreadStatic]
    private ISession _session;

    [ThreadStatic]
    private ITransaction _transaction;

    public NHibernateUnitOfWork(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
        _session = _sessionFactory.OpenSession();
        _transaction = _session.BeginTransaction();
    }

    public static ISession CurrentSession { get { return _session; } }

    public void Dispose()
    {
        _transaction = null;
        _session.Close();
        _session = null;
    }

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

    public void Rollback()
    {
        if (_transaction.IsActive) _transaction.Rollback();
    }
}

public class Repository : IRepository
{
    public void Add(IObj obj)
    {
        if (NHibernateUnitOfWork.CurrentSession == null)
            throw new Exception("No unit of work present");

        NHibernateUnitOfWork.CurrentSession.Save(obj);         
    }
}

// ASP.NET MVC controller, but valid for any
// other arbitary application service
public class MyController : Controller 
{
    private readonly IPeopleRepository _repository;

    // di -> declaring IPeopleRepository dependency
    public MyController(IPeopleRepository repository) {
        _repository = repository;
    }

    public void AddPerson(Person person) 
    {
        using (IUnitOfWork uow = new NHibernateUnitOfWork())
        {
            try
            { 
                _repository.Add(person);
                uow.Commit();
            }
            catch(Exception ex)
            {
               uow.RollBack();
            }
        }
    }
 }

虽然这是处理问题的一种方法,但有一些方法可以更聪明地做,一个是使用ActionFilter,它在动作之前启动事务并在所有成功时提交,或者你可以使用HttpModule来处理交易处理..

您可以选择一个完全不同的路径并实现命令模式,其中每个操作都是一个命令,无论它有多复杂,处理程序应该启动并提交事务,请查看https://fnhmvc.codeplex.com/

<强> 2。如果使用正确的映射和用于删除父实体子实体的正确UOW将被自动删除

第3。如果使用上述工作单元模式,则不会出现问题

public SomethingService:ISomethingService {     public ISomethingService(IFirstRepo repo1,ISecondRepo repo2,IUnitOfWork uow)     {     ...     }

public void DoSomething() 
{
    using (IUnitOfWork uow = new NHibernateUnitOfWork())
    {
        try
        { 
            repo1.AddThis();
            repo2.GetThisOne();
            repo2.BecauseOfTheOneAboveDeleteThis();
            uow.Commit();
        }
        catch(Exception ex)
        {
           uow.RollBack();
        }
    }
}

答案 3 :(得分:0)

我会尝试回答。

1.场景:将单个实体插入存储库

您可以将方法void SaveChanges()添加到您的存储库界面,这没关系。

2.删除应删除链接实体的聚合

有很多方法可以做到这一点。首先,您应该决定是否负责删除关联对象的聚合根或存储库?

如果是聚合根责任,那么

  1. 您需要使用IAggregateRoot
  2. 填充void OnDelete()
  3. 删除前在存储库中调用它。
  4. OnDelete的实现应手动删除所有关联的对象,因此您需要
  5. 定义IDeleteStrategy,它将像Only-Delete-Repository一样工作。区别在于IDeleteStrategy方法的参数限制 - 它必须处理实体(!),而不仅仅是聚合根。并且存储库可以通过委派删除操作来重用它。
  6. 最后,您应该将IDeleteStrategy合并到要删除的每个实体类型的聚合根目录中。是的,它是聚合根中的依赖注入。
  7. 这个解决方案很好,因为:

    1. 聚合根管理聚合内部,因此没有聚合封装违规。
    2. 如果满足某些内部聚合条件,聚合根可能会抛出异常,因此无法完成删除。
    3. 也许,你可以保存你的存储库更简单和通用(就我而言,根据DDD,这是非常好的)
    4. 如果是存储库责任,则应创建非通用存储库并为每个聚合实现自定义删除逻辑。

      最后一个。有一个原则告诉我们,对象的创造者对其的生命负责。因此,工厂可能是清理内部聚合的好地方。

      3.Scenario:在两个以上的存储库上执行交易

      有一个技术限制,需要使用单个DbConnection来执行事务/ UoW。解决方案强烈依赖于您使用的技术。因此,我可以快速提出的唯一方法是强制您的环境在每个请求/用户会话中使用单个上下文(EF?)/ connection / session。

      另一种可能性是实现某种事务级别(在域和DAL之间),但它也取决于您使用的技术。