我一直在阅读DDD,我很困惑在使用像NHibernate这样的ORM时它会如何适应。
现在我有一个带有相当“胖”控制器的.NET MVC应用程序,我正在试图弄清楚如何最好地解决这个问题。将这个业务逻辑移到模型层中是最好的方法,但我不确定如何做到这一点。
我的应用程序已设置好,以便NHibernate的会话由HttpModule管理(从我的方式获取会话/事务),由返回实体对象的存储库使用(Think S#arp arch ...结果是真的重复了很多他们的功能)。这些存储库由DataServices使用,它现在只是存储库的包装(它们之间的一对一映射,例如UserDataService采用UserRepository,或实际上是存储库)。这些DataServices现在只确保在保存/更新时检查装饰实体类的数据注释。
通过这种方式,我的实体实际上只是数据对象,但不包含任何真实的逻辑。虽然我可以在实体类中放置一些东西(例如“批准”方法),当该操作需要执行诸如发送电子邮件或触摸其他非相关对象之类的操作时,或者,例如,检查是否有任何用户在批准之前拥有相同的电子邮件等,那么实体将需要访问其他存储库等。用IoC注入这些用户不适用于NHibernate,所以你必须使用工厂模式我假设得到这些。我不知道你怎么会在测试中嘲笑那些。
因此,我认为,下一个最合乎逻辑的方法是,每个控制器基本上都有一个服务,并将当前控制器中正在完成的所有工作提取到每个服务的方法中。我认为这会破坏DDD的想法,因为逻辑现在不再包含在实际的模型对象中。
另一种看待我的想法是,这些服务中的每一个都与它所使用的数据对象形成一个单一的模型(数据存储区域的分离和对其进行操作的逻辑),但我只是想看看其他人正在做什么来解决DDD的“胖控制器”问题,同时使用像NHibernate这样的ORM,它通过返回填充的数据对象和存储库模型来工作。
更新 我想我的问题是我在看这个问题:NHibernate似乎把业务对象(实体)放在堆栈的底部,然后这些存储库依赖于它们。服务器使用存储库,这些服务可以使用多个存储库和其他服务(电子邮件,文件访问)来执行操作。即:App>服务>存储库> Business Objects
我正在阅读的纯DDD方法似乎反映了Active Record偏差,其中CRUD函数存在于业务对象中(我直接调用User.Delete而不是来自服务的Repository.Delete),以及实际的业务对象处理在此实例中需要完成的事情的逻辑(如通过电子邮件发送用户,删除属于该用户的文件等)。即应用> (服务)> Business Objects>存储库
使用NHibernate,似乎我会更好地使用NHibernate函数的第一种方法,并且我正在寻找对我的逻辑的确认。或者如果我感到困惑,可以对这种分层方法如何工作进行一些澄清。我的理解是,如果我有一个“批准”方法来更新用户模型,保留它,并且让我们说,给少数人发电子邮件,这个方法应该放在用户实体对象上,但允许适当的IoC,所以我可以注入messagingService,我需要在我的服务层而不是User对象上执行此操作。
从“多UI”的角度来看,这是有道理的,因为做事的逻辑是从我的UI层(MVC)中取出,并放入这些服务......但我基本上只是将逻辑分解到了另一个类,而不是直接在控制器中进行,如果我不打算涉及任何其他UI,那么我只是为了“胖服务”交易了一个“胖控制器”,因为服务本质上是将每个控制器动作封装一个方法来完成它的工作。
答案 0 :(得分:4)
DDD 不具有Active Record倾斜。 删除是不一种应该位于DDD中的实体(如用户)上的方法。
NHibernate确实很好地支持DDD方法,因为它与实体类完全脱离。
当该行动需要做某事时 比如发送电子邮件或触摸 其他不相关的对象
您似乎缺少的一个难题是域事件。域实体不应直接发送电子邮件。它应该在域中引发一个重大事件发生的事件。实现一个类,其目的是在事件发生时发送电子邮件,并注册它以侦听域事件。
或者,例如,检查是否 有任何用户都有相同的 批准前发送电子邮件
在提交“批准”调用之前,可能应该检查,而不是在执行批准的函数中。将决定推进调用代码的水平。
所以下一个最合理的方式, 我想,基本上就是这样 为每个控制器提供服务
如果在了解服务是客户端的入口点的情况下完成,则可以工作。此处服务的目的是从前端/客户端接收DTO中的参数,并将其转换为针对实体的方法调用,以便执行所需的功能。
答案 1 :(得分:0)
对你的简短回答是肯定的,事实上,我发现NHibernate增强了DDD - 你可以专注于使用代码优先方法开发(和改变)你的域模型,然后使用NHibernate轻松地改进持久性。 / p>
当您在DDD之后构建域模型时,我希望您在MVC控制器中找到的大部分业务逻辑可能都存在于您的域对象中。在我第一次尝试使用ASP.NET MVC时,我很快发现自己处于与自己相同的位置 - 胖控制器和贫血域模型。
为了避免这种情况,我现在遵循保持实现业务逻辑的富域模型的方法,并使用MVC的模型作为我的视图使用的基本上简单的数据对象。这简化了我的控制器 - 它们与我的域模型交互,并提供简单的数据对象(从MVC模型)到视图。
<强>更新强>
我正在阅读的纯DDD方法似乎反映了Active Record的偏见......
对我来说,活动记录模式意味着实体知道它们的持久性机制,实体直接映射到数据库表记录。这是使用NHibernate的一种方法,例如然而,请参阅Castle Active Record,我发现这会污染域知识并了解其持久性机制。相反,通常,我的域模型中每个aggregate root都有一个存储库,它实现了一个抽象存储库。抽象存储库提供基本的CRUD方法,例如:
public IList<TEntity> GetAll()
public TEntity GetById(int id)
public void SaveOrUpdate(TEntity entity)
public void Delete(TEntity entity)
..我的具体资料库可以补充/扩展。
请参阅this post on The NHibernate FAQ,其中包含了很多我的内容。还要记住,NHibernate(取决于你如何设置映射)将允许你去除一个完整的对象图,即你的聚合根以及挂起它的所有对象,一旦你完成了它,就可以级联保存通过你整个对象图,这当然不是活跃的记录。
...因为该服务实质上是为每个控制器动作封装一个方法来完成它的工作......
我仍然认为您应该考虑您的控制器中当前的功能应该更合乎逻辑地在域对象中实现。例如在您的批准示例中,我认为实体公开批准方法是明智的,该方法在实体中执行任何需要做的事情,如果在您的示例中需要发送电子邮件,则将其委托给服务。应为cross-cutting concerns保留服务。然后,在完成域对象的处理后,将它们传递回存储库以保持更改。
我发现有几本关于这些主题有用的书籍是:
Domain-Driven Design by Eric Evans
Applying Domain-Driven Design and Patterns by Jimmy Nilsson
答案 2 :(得分:0)
NHibernate为类创建的唯一限制是所有方法/属性必须是虚拟的,并且类必须具有默认构造函数(可以是内部的或受保护的)。否则,它不会[编辑]干扰对象结构,并且可以映射到非常复杂的模型。