存储库、管道、业务逻辑和域模型 - 我如何将它们组合在一起?

Posted

技术标签:

【中文标题】存储库、管道、业务逻辑和域模型 - 我如何将它们组合在一起?【英文标题】:Repository, Pipeline, business logic and domain model - how do I fit these together? 【发布时间】:2011-08-29 18:44:10 【问题描述】:

我正在设计 N 层应用程序,遇到了一个您可能有解决方案的难题。表示层是 MVC。

我的 ORM 是使用 LinqToSQL 执行的 - 它是一个单独的项目,为存储库提供服务。

每个存储库都有一个接口和至少 1 个具体实现。

存储库有以下方法:FindAll(), Save(T entity), Delete(int id)

FindAll() 返回某种类型的 IQueryable,这意味着它返回我可以应用过滤器的查询。

ORM 映射是使用 Database First 方法进行的,其中首先创建表,然后由 SQL Metal 生成类。

我添加了一个与存储库一起使用的管道层。它将进一步的过滤器应用于查询。例如。 OrderRepository.FindAll().Where(o => o.CustomerId == 10)

Pipeline 还返回某种类型的 IQueryable,这意味着我可以将它向上传递到层并用它做更多的事情。

此时我想移动到业务逻辑层,但我不想再使用实体模型,我想将实体模型转换为领域模型。这意味着我可以向模型添加验证并在表示层中使用该模型。模型不能在 MVC 项目中定义,因为它依赖于表示层,所以不能。

我相当肯定业务逻辑(行为)和模型必须与管道、数据和表示层分开存储。问题是在哪里?

例如,管道具有三种方法: 1. FindByCustomerId 2. FindByOrderId 3. FindBySomethingElse

所有这些方法都返回 IQueryable of Order。我需要将其转换为域模型,但我不想按每种方法都这样做,因为它无法维护。

我觉得这个模型相当健壮且可扩展。我只是不知道从实体映射到域模型的最佳位置是什么,反之亦然。

谢谢

【问题讨论】:

【参考方案1】:

首先,如果你在这里应用领域驱动设计原则,你的应用程序中一定不能有业务逻辑层。所有业务逻辑都应该存在于您的域模型中。

但是使用 LinqToSQL 很难实现,因为它不支持继承映射,并且您必须处理部分类才能将业务逻辑放入您的域中。所以我强烈建议考虑从 LinqToSQL 迁移到 NHibernate 或 Entity Framework Code First 。在这种情况下,您也不必将持久性模型转换为域模型,反之亦然。

如果你还想做转换,你可以看看Automapper

【讨论】:

感谢您的回复。我将研究实体框架 - 我的印象是 EF 和 LINQ-to-SQL 基本上是相同的,其中 LINQ-To-SQL 是数据库优先的方法。 我正在考虑重构数据层以使用实体框架,但我担心它可能需要太长时间。此外,实体框架会有一个学习曲线,必须重新编写大部分单元测试,因为它们必须使用领域模型,而不是实体模型。 我同意,但需要注意的是: 逻辑应该驻留在域模型中,但您可能需要执行其他操作(应用程序逻辑) 调用某个领域模型方法时。例如,您可能想要通知第三方。这样做通常需要访问INotificationService,我相信域模型不应该知道这样的事情。 Fowler 对 域逻辑应用程序逻辑 进行了区分,并建议第二个应该驻留在 ServiceLayer 中 - 这样,域模型和服务层可以很好地协同工作。 当然,应用程序逻辑应该与域逻辑分离,但它不应该依赖于域模型本身,而应该只依赖于域事件。还有一些在领域层无法解决的横切关注点。我将 CQRS 命令和事件处理程序用于此类操作。【参考方案2】:

从领域驱动的角度来看,您需要一个工厂来将您的“数据库实体”转换为领域模型实体。

当您考虑在管道结束时将“数据库实体”转换为域模型实体时,您应该意识到在转换为域模型实体(投影)之后,您将无法使用 IQueryable投影功能将触发表达式树的执行。例如,如果您为客户数据库实体调用 FindAll,然后将 IQueryable 转换为(或将其投影到)它将执行的客户域实体(请求整个表的内容)。

【讨论】:

您好,感谢您的回复。这是迄今为止的主要问题。我必须转换为域模型,然后将该模型传递给表示层。这适用于数据检索例程,但是当您开始创建/更新数据时,它就会陷入困境。我需要将域模型转换回实体模型,到目前为止,我还没有看到一种优雅的方法。一旦它转换为实体模型,我就必须运行创建/更新例程并将其转换回域模型,到目前为止这一直是一场噩梦。 IQueryable 和管道的使用很棒——我真的很喜欢管道。一个 ASP.Net storefrong 视频鼓励使用 LazyList,这是非常基本的,但它似乎并不正确。不能完全证明它为什么不好,但肯定有更优雅的方法。 您希望数据库实体和域模型实体分开是否有特定原因?这确实创建了一个抽象,但现在有必要这样做吗?虽然听起来要让它们分开,但以后可以添加(考虑到简洁的设计) 嗨,目前我在表示层中有一个视图模型,它 90% 重复了一个域模型。另一个问题是我的业务逻辑没有封装。我想把它放在一个单独的层中,这样我就可以从这个应用程序中取出业务层并在不同的应用程序中使用它。目前我不能这样做,因为逻辑无处不在:模型位于实体形式的数据层中。业务方法在某种程度上位于服务层。我已经提到过视图模型。我也发现很难为实体编写创建/更新例程。 项目一天比一天大。如果我现在不解决这些问题,我怕我以后会面临后果。到那时,一切都将紧密耦合,我将无能为力。这些是我的担忧。【参考方案3】:

这就是我做 N 层项目的方式。这种架构具有很好的关注点分离。听起来你已经朝着这个方向前进了。

在 Mvc 项目中是所有常用对象(控制器、视图模型、视图、助手等)。非常坦率的。所有视图都是强类型的。加上我修改过的 T4 模板,它们生成控制器、视图和视图模型。

在业务模型项目中,我拥有所有业务对象和规则,其中包括定义数据存储库功能的接口。我更喜欢按功能对我的每个业务对象/表进行分组,而不是为每个业务对象/表创建一个存储库。与博客相关的所有对象都在一个存储库中,而与照片库相关的所有对象都在一个单独的存储库中,而日志记录可能在第三个存储库中。

你可以把你的管道层放在这里。

在数据项目中,我实现了那些数据存储库接口。您可以使用 Linq2SQL 而不必使用部分类。扩展这些 Linq2SQL 部分类意味着您已将您的 ORM 绑定到您的域模型。你真的不想做的事情。您希望将这些生成的数据类留在数据域中。这是一个返回 BusinessModel 对象的示例 Linq2SQL 选择。

from t in Table
where t.Field == keyField
select new BusinessModel.DataObject

  Id = t.Id,
  Field1 = t.Field1,
  Field2 = t.Field2

如果我是你,我会使用 CodeFirst 方法或使用 NHibernate 来查看 EntityFramework 4.1。其中任何一个都会将您的数据模型映射到域模型。然后要将领域模型映射到视图模型,您可以使用 AutoMapper 或编写自定义代码或编写 T4 模板来为您生成映射代码。

您可以将 dbml 文件生成的代码作为业务对象的起点。

【讨论】:

我刚刚有了另一个想法。获取 Subsonic 的副本并查看 ActiveRecord.tt 文件。它将向您展示如何从数据库生成类文件。这可能比从 DBML 文件生成的代码中删除一堆不必要的代码更容易和更快。【参考方案4】:

除了 xelibrion 的 cmets,您还可以查看 LightSpeed 以满足您的 ORM 需求。您目前正在使用 LinqToSQL,因此您应该会发现 Lightspeed 非常简单,因为它使用了相同的想法。

http://www.mindscapehq.com/products/lightspeed

如果您可以将数据映射到更符合您的更高级别想要的形式的模型,那么希望您可以简化事情。系统的复杂性越低,错误的范围就越小。

【讨论】:

【参考方案5】:

所有这些方法都返回 IQueryable 的 命令。我需要将其转换为 域模型,但我不想这样做 每种方法都不会,因为它不会 可维护。

这不是一个真实的评估,可能会阻止您看到正确的解决方案。

【讨论】:

关心扩展一下是不是真的?里面有几种不同的说法。 IQuerable of Order 是一个尚未对数据库执行的查询,因此我可以在管道中对该查询应用进一步的过滤器。然后我会在这个查询上调用 ToList() 或 SingleOrDefault() 以获取实际对象。 您正在处理多少表和模型使其无法维护? 17 张桌子。其中一张桌子与另外 4 张桌子相连。

以上是关于存储库、管道、业务逻辑和域模型 - 我如何将它们组合在一起?的主要内容,如果未能解决你的问题,请参考以下文章

业务逻辑和规则——如何将它们与领域模型解耦

服务层如何适应我的存储库实现?

存储库模式与“智能”业务对象 [关闭]

MVC 4 - 在哪里保留业务逻辑

使用 Jenkins 管道将多个 git 存储库签出到同一个作业中

如何在 ASP MVC 中实现工作单元、存储库和业务逻辑?