DDD 和 ADO.NET Entity Framework






3.60/5 (8投票s)
将领域驱动设计 (DDD) 与 ADO.NET Entity Framework 结合使用。
引言
DDD,即领域驱动设计,引起了广泛关注。有很多文章和博客文章介绍了 DDD 如何与这项或那项技术协同工作。或者如何使用 DDD 和 X 来构建应用程序?自然而然地,我也产生了兴趣,并决定了解它与 Entity Framework 的工作方式。
领域驱动设计简述
DDD 是一种创建软件的架构理念。其主要思想是,应用程序的设计应基于模型,而不是技术或其他任何东西。这不仅对开发人员来说很清晰,对该领域的专家也是如此。
领域驱动设计基于五种对象
- 实体 (Entities):这些对象代表应用程序的核心。它们表达业务,并被客户理解。更重要的是,每个实体都有一个身份,并且在该系统内是唯一的。
- 值对象 (Value Objects):这些对象没有身份,只描述特征。
- 服务 (Services):服务包含不属于特定实体的逻辑。
- 工厂 (Factories):这是一种众所周知的模式,负责创建实体。
- 仓储 (Repositories):仓储用于分离实体及其在数据库中的表示。它们包含负责与数据库交互的代码。
这里是 维基百科上的 DDD。
实体框架
简单介绍一下 Entity Framework (EF) 是什么。根据维基百科,Entity Framework 就是一个 ORM。不需要有博士学位就能明白,基于数据集的方法来处理数据库持久化并不总是有效,尤其是在大型项目中。实际上,EF 是微软的第一个 ORM(LINQ to SQL 不算)。
从框架的名称很容易猜到,EF 的核心概念是……EntityObject。EntityObjects 代表包含需要持久化信息 的对象。为了实现这一点,EF 使用三个基于 XAML 的模型和映射文件来处理实际数据。
从用户的角度来看,EF 使持久化任务变得非常简单。Visual Studio 会从数据库生成所有必需的 EntityObject 类,用户无需处理 `DataSet` 和创建 `SqlConnection`,而是直接处理代表业务对象的类。这里是 MSDN 上的 Entity Framework。
DDD 实体 vs. EF EntityObjects
当人们考虑使用 DDD 和 EF 创建新应用程序时,首先想到的就是将 EF EntityObjects 用作 DDD 实体。
EntityObjects 是唯一的,并且确实有身份,这符合实体的要求。然而,它们无法与 Entity Framework 分离。根据 DDD 的方法,领域对象应仅代表业务逻辑,它们不应负责存储或显示自身或任何其他内容。这个概念也称为持久化无关性 (Persistence Ignorance),意味着领域对象不关心数据如何从数据源保存或检索。
不幸的是,EF 不是一个 PI ORM,无法让 EntityObjects 独立于框架。它们的代码是基于数据库架构和映射文件自动生成的。理论上,可以创建“真正的”实体,并将 EntityFramework 仅用作数据持久化层。然而,这是非常荒谬的,因为在这种情况下,将需要重新实现 EF 提供的所有持久化模式(工作单元、身份映射器等)。
因此,从 DDD 的角度来看,EntityFramework 充当仓储,EntityObjects 充当其集成的实体。这当然是一个可行的解决方案,但许多人更希望拥有不了解底层数据源的领域对象。
持久化无关
持久化无关性是人们争论已久的问题。有些人认为它很好,有些人认为它不错但并非总是必需。
以下是持久化无关框架不得强加给开发人员领域对象的限制:
- 要求继承除 `Object` 之外的指定基类
- 要求使用工厂方法来实例化它们
- 要求为属性(包括集合)使用专门的数据类型
- 要求实现特定的接口
- 要求使用特定于框架的构造函数
- 要求使用特定的字段或属性
- 禁止特定的应用程序构造
- 要求以提供程序特定的方言编写 RDBMS 代码,例如查询或存储过程调用
即使能够轻松更换数据库也很好,但并非所有人都需要。PI 更实际的目标是能够更轻松地模拟数据库对象,从而简化单元测试。
领域对象与持久化逻辑分离的支持者对 EF 提出的方法并不满意。他们的声音得到了听取,并承诺 EF v2 将兼容 PI。
目前,可以使用 EF 中提供的中间解决方案。这就是所谓的 IPOCO 模式。
IPOCO
POCO 是 Plain Old DLR Object 的缩写,IPOCO 是 Interface POCO 的缩写。POCO 是不包含任何复杂、特定于框架代码的对象。例如,Entity Framework 中的 EntityObjects 不是 POCO,因为它们包含很多 ORM 特定的代码。IPOCO 并非实际接口,而是一种模式,它要求实现接口而不是派生自特殊对象来使用 ORM。
Entity Framework 团队迈向 PI 的第一步是在当前版本的框架中引入了 IPOCO 兼容性。
需要由自定义类实现的三个接口:
IEntityWithChangeTracker
用于更改跟踪IEntityWithKey
用于公开实体标识键(可选,但不实现此接口会显著降低性能)IEntityWithRelationships
是具有关联的实体所必需的
如果您决定采用这种方式并使用 IPOCO 而不是 EdmGen.exe 生成的类,您应该意识到即将面临的工作量。您不仅要实现接口,还要用 EDM 特定的属性来装饰您的类,以便为框架提供必要的元数据。此外,您将失去自动属性功能的便利性,因为每个属性都需要更改跟踪功能。基本上,您在生成的 EntityObject 中看到的所有内容,都将需要自己完成。
为了更好地理解所需的编码,请参阅 MSDN 文章,其中包含所有接口的示例实现。
使用 EF 设计应用程序
EF 缺乏 PI 并不意味着它毫无价值。一点也不。它只是一种不同的方法。
我们假设我们仍然遵循 DDD,只是我们的领域对象将依赖于数据库层。这不太好,但我们希望在可预见的未来不需要更换数据库。
第一个出现的问题是,我们将领域特定逻辑放在哪里?由于 EntityObjects 是自动生成的,直接扩展它们是个糟糕的主意;相反,我们可以创建部分类来包含我们需要的所有内容。
不要忘记 EntityFramework 不仅仅是一组自动生成的类。它的 ObjectContext 将成为我们的身份映射和工作单元,因此我们的仓储将非常简单。它们将充当所有数据库交互代码的存储。这将使实体摆脱它们不需要的代码,并为我们提供在单元测试中模拟查询的机会。
我们可以再做一件事,这可能会在将来我们决定更换 ORM 时有所帮助。我们可以创建接口,并让我们的实体对象实现它们。这将使我们的生活复杂化一点,但我们会有一种“独立”的错觉,谁知道呢,也许有一天会有用……这种架构的一个例子可以在 这里 找到。
结论
如果背离 PI 思想并非至关重要,那么可以说 EF 和 DDD 可以一起使用。如果至关重要……总有其他选择。您可以通过使用 IPOCO、等待 EF v2 或使用其他 ORM(据说 NHibernate 完全兼容 PI)来实现领域对象的更高抽象级别。