架构问题:Fluent NHibernate、仓储模式和ASP.NET MVC

6
我刚开始了一个新项目,自然而然地选择使用了很多新技术。我正在使用(Fluent) NHibernate、ASP.NET MVC 3,并尝试应用存储库模式。我决定将业务逻辑分离到一个单独的项目中,并定义服务来包装我的存储库,以便我可以返回POCOs而不是NHibernate代理,并在前端和DA逻辑之间保持更多的分离。这也将使我能够轻松地以后提供相同的逻辑作为API(要求)。我选择使用通用的>接口,其中T是我的NHibernate映射实体之一,它们都实现了IEntity(我的接口只是一个标记而已)。问题是,这与聚合根模式相违背,我开始感受到了无血统域模型的痛苦。如果我改变了另一个对象,那么在我的服务中,我必须执行以下操作:
public void AddNewChild(ChildDto child, rootId)
{
    var childEntity = Mapper.Map<ChildDto,ChildEntity>(child);
    var rootEntity = _rootrepository.FindById(rootId);
    rootEntity.Children.Add(childEntity);
    _childRepository.SaveOrUpdate(child);
    _rootRepository.SaveOrUpdate(root);
}

如果我不先保存孩子,NHibernate会抛出异常。我觉得我的通用仓储库(目前在一个服务中需要5个)不是正确的方法。

 public Service(IRepository<ThingEntity> thingRepo, IRepository<RootEntity> rootRepo, IRepository<ChildEntity> childRepo, IRepository<CategoryEntity> catRepo, IRepository<ProductEntity> productRepo)

我感觉我的代码变得越来越脆弱,而不是更加灵活。如果我添加一个新的表格,我需要去改变所有测试中的构造函数(我正在使用DI进行实现,所以这还不算太糟糕),但这似乎有点不妙。
有人有关于如何重构这种架构的建议吗?
我应该让我的仓库更具体化吗?服务抽象层是否有点过头了?
编辑:有一些很好的相关问题可以帮助:
- 仓储模式最佳实践 - 仓储模式帮助 - 架构难题

1
很有可能你的NHibernate映射不正确。 - Vadim
看起来我需要添加.Cascade().All()(我正在使用Fluent NHibernate),但还是谢谢你提醒我! - Rob Stevenson-Leggett
1
@Rob - 简而言之,我的看法是即使仓库也是多余的一层,你看到的不断脆化是一个警告!如果你还没有阅读过 Repository is the new Singleton,它让我相信你可以直接针对 NHibernate 编写应用程序。The Onion Architecture 文章也很相关(并且有趣)。 - Jeff Sternal
@ebb 并不是所有的服务都是如此,有几个服务比较难以操作。 - Rob Stevenson-Leggett
@Rob:使用AutoMapper还是不使用,都会从外部操纵(本应该是)领域对象的内部状态。我总是认为这是贫血领域的产生之路。看看任何给定的属性,问问自己:这个属性可能在多少地方被更改。再次强调,只是试图引发一些思考,以避免贫血领域。 - quentin-starin
显示剩余5条评论
2个回答

2
当你拥有一个聚合时,仓储对于聚合的父对象(根对象)和它的子对象是相同的,因为子对象的生命周期由根对象控制。
你的根对象类型的“保存”方法应该直接负责将更改持久化到子记录,而不是将这个责任委托给另一个仓储。
此外,在“正确”的聚合模式中,子记录没有自己的标识符(至少在聚合外部是不可见的)。这有几个直接的后果:
  1. 不能从外部记录/聚合到这些子记录设置外键。
  2. 由于第1点,每当保存根对象状态时,您可以在数据库中删除并重新创建子记录。如果遇到了先前存在的问题,这通常会使持久性逻辑更加容易。
注意:1的反向情况并非如此。聚合中的子记录可以具有指向其他根记录的外键。

谢谢,这很有帮助。第二个问题呢 - 针对服务层的抽象化? - Rob Stevenson-Leggett
在绝对必要之前,我会忘记服务层。通常情况下,服务层用于实现高级操作,涉及多个存储库、长时间运行的进程或其他基本CRUD之外的内容。 - miguelv
我有一个复杂的领域,有许多规则。我不太舒服让这个逻辑驻留在我的控制器中,因为它在两个不同的应用程序之间共享,并且将作为一个REST服务提供。 - Rob Stevenson-Leggett
那么你有充分的理由来定义服务。我所指的是更简单的逻辑,比如你示例中的逻辑,它应该属于一个仓库内部。 - miguelv

1
我觉得我的代码变得越来越脆弱,而不是更加灵活。如果我添加一个新的表格,我需要去修改所有测试中的构造函数(我使用 DI 进行实现,所以情况不太糟),但这似乎有点不好。
在你的仓库中实现 Unit Of Work 模式。这意味着你需要一个 UnitOfWork 类,它通过构造函数或属性注入来持有你的服务。此外,它还持有一个提交和/或事务方法。只需将 IUnitOfWork 实例注入到你的服务中即可。当你添加一个仓库时,你只需要改变 UnitOfWork 而不用触碰业务逻辑(服务)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接