仓库、流水线、业务逻辑和领域模型——这些如何相互配合?

14
我正在设计N层应用程序,遇到了一些困难,您可能有解决方案。演示层是MVC。
我的ORM使用LinqToSQL进行,这是一个独立的项目,用于提供存储库。
每个存储库都有一个接口和至少一个具体实现。
存储库具有以下方法:FindAll(),Save(T entity),Delete(int id)
FindAll()返回某种类型的IQueryable,这意味着它返回查询,可以对其应用过滤器。
使用先创建表,然后通过SQL Metal生成类的“数据库优先”方法进行ORM映射。
我添加了一个Pipeline层来处理存储库。它将进一步过滤查询。例如:OrderRepository.FindAll()。Where(o => o.CustomerId == 10)
Pipeline还返回某种类型的IQueryable,这意味着我可以将其传递到更高的层并使用更多功能。
此时,我想转移到BusinessLogic层,但我不想再使用实体模型,我想将实体模型转换为域模型。这意味着我可以向模型添加验证并在演示层中使用该模型。不能在MVC项目中定义模型,因为它将依赖于演示层,所以不行。
我相当确定业务逻辑(行为)和模型必须与管道、数据和演示层分别存储。问题是在哪里?
例如,管道有三个方法: 1. FindByCustomerId 2. FindByOrderId 3. FindBySomethingElse 所有这些方法都返回Order的IQueryable。我需要将其转换为域模型,但我不想每个方法都这样做,因为它将无法维护。
我觉得这个模型相当强大和可扩展。我只是看不到从实体到域模型的最佳映射位置。
谢谢。
5个回答

13

首先,如果您正在应用领域驱动设计原则,那么您的应用程序中不应该有BusinessLogic层。所有业务逻辑都应该存在于您的领域模型中。

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

如果您仍然想进行转换,可以看看Automapper


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

12

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

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


你好,感谢您的回复。这是目前的主要问题。我必须将其转换为领域模型,然后将该模型传递到表示层。对于数据检索例程,这很好用,但在开始创建/更新数据时会失败。我需要将领域模型转换回实体模型,到目前为止,我还没有看到一种优雅的方法来做到这一点。一旦它被转换为实体模型,我就必须运行创建/更新例程并将其转换回领域模型,这到目前为止一直是一场噩梦。 - user338195
使用IQueryable和管道非常棒 - 我真的很喜欢管道。其中一个ASP.Net商店视频鼓励使用LazyList,这是相当基础的,但似乎不太对。无法确切地证明它有什么问题,但肯定有更优雅的方式。 - user338195
你想让数据库实体和领域模型实体分开的具体原因是什么?这确实会产生一种抽象,但现在有必要这样做吗?虽然保持它们分开听起来不错,但如果设计良好,这是可以稍后添加的。 - thekip
目前,我在演示层中有一个视图模型,其中90%重复了域模型。另一个问题是我的业务逻辑没有封装。我想将其放在单独的层中,这将使我能够将业务层从此应用程序中取出并在不同的应用程序中使用它。目前,我无法做到这一点,因为逻辑散落在各个地方:模型以实体的形式位于数据层中。业务方法在某种程度上在服务层中。我已经提到了视图模型。此外,我发现很难编写实体的创建/更新例程。 - user338195
这个项目日益庞大。如果我现在不解决这些问题,我担心以后会面临后果。到那时,一切都将紧密耦合,我将无法做太多事情。这些是我的顾虑。 - user338195

8
这是我处理N-Tier项目的方法。这种架构具有很好的关注点分离。听起来你已经朝着这个方向走了。
在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文件生成的代码作为业务对象的起点。

1
我又想到了一件事。获取Subsonic的副本并查看ActiveRecord.tt文件。它将向您展示如何从数据库生成类文件。这可能比从DBML文件生成的代码中剥离大量不必要的代码更容易和更快。 - 37Stars

3
根据xelibrion的评论,你可以考虑使用LightSpeed来满足ORM需求。由于它使用相同的思路,因此你目前正在使用LinqToSQL,所以应该会发现Lightspeed非常简单易懂。
如果你可以将数据映射到更符合高级别表单的模型中,那么希望你可以简化事情。系统的复杂度越低,出错的可能性就越小。请参考http://www.mindscapehq.com/products/lightspeed

0
所有这些方法都返回Order的IQueryable。我需要将其转换为域模型,但我不想每个方法都这样做,因为这样做是不可维护的。
这并不是一个真正的评估,很可能会阻止您看到正确的解决方案。

能否详细说明哪些内容是不正确的?这里有几个不同的陈述。 - jlew
IQuerable of Order 是一个尚未对数据库执行的查询,因此我可以在管道中对此查询应用进一步的过滤器。然后,我会调用 ToList() 或 SingleOrDefault() 在此查询上,以获取实际对象。 - user338195
你处理了多少张表和模型,以至于它变得难以维护? - Todd Smith
17个表格。其中一个表格与另外4个表格连接。 - user338195

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