如何让我的 SQL DB 与我的领域驱动设计相匹配
Posted
技术标签:
【中文标题】如何让我的 SQL DB 与我的领域驱动设计相匹配【英文标题】:How to get my SQL DB to match my Domain Driven Design 【发布时间】:2009-05-06 14:06:21 【问题描述】:好的,我会直截了当地告诉你们:我不确定我的设计究竟是如何实现领域驱动的,但我确实从构建模型对象开始并完全忽略了持久层。现在我很难决定在 SQL Server 中构建表以匹配模型的最佳方式。
我正在用 ASP.NET MVC 构建一个 Web 应用程序,尽管我认为平台并不重要。我有以下对象模型层次结构:
属性 - 具有地址和邮政编码等属性 有一个或多个 Case - 继承自 PropertyObject Quote - 继承自 PropertyObject 有一个或多个 Message - 具有 Reference、Text 和 SentDate 属性的简单类Case 和 Quote 有很多相似的属性,所以我也有一个 PropertyObject 抽象基类,它们继承自。所以 Property 有一个 List 类型的 Items 属性,它可以包含 Case 和 Quote 对象。
所以本质上,我可以拥有一个包含一些引用和案例以及可以属于其中任何一个的消息负载的属性。
PropertyObject 具有 Reference 属性(因此 Quote 和 Case 也是如此),因此任何 Message 对象都可以通过其 Reference 属性与 Quote OR Case 相关联。
我正在考虑使用实体框架将我的模型导入和导出数据库。
我最初的想法是有四个表:Property、Case、Quote 和 Message。
它们都有自己的顺序 ID,并且 Case 和 Quote 将通过 PropertyID 字段与 Property 相关联。
我能想到的将 Message 表关联回 Case 和 Quote 表的唯一方法是同时拥有 RelationID 和 RelationType 字段,但是没有明显的方法可以告诉 SQL Server 这种关系是如何工作的,所以我不会没有任何参照完整性。
有什么想法、建议、帮助吗?
谢谢, 安东尼
【问题讨论】:
【参考方案1】:我假设 Property 也不继承自 PropertyObject。
鉴于这些表,Property、Case、Quote 和 Message,导致每个具体类或 TPC 继承策略的表,我通常不推荐。
我的建议是您可以使用:
每个层次结构或 TPH 的表 - Case 和 Quote 存储在同一个表中,其中一列用作鉴别器,不共享的属性具有可为空的列。 每个类型或 TPT 的表 - 添加一个包含共享字段的 PropertyObject 表以及仅包含这些类型的额外字段的 Case 和 Quote 表这两种策略都可以让您保持参照完整性,并且得到大多数 ORM 的支持。
查看更多信息:Tip 12 - How to choose an inheritance strategy
希望这会有所帮助 亚历克斯
【讨论】:
感谢 Alex,那篇文章帮助我下定决心选择 TPT。【参考方案2】:啊……抽象。
DDD 的诀窍是认识到抽象并不总是你的朋友。在某些情况下,过多的抽象会导致关系模型过于复杂。
您并不总是需要继承。事实上,继承的主要目的是重用代码。重用结构可能很重要,但不那么重要。
你有一个突出的 is-a 关系:Case
IS-A Property
和 Quote
IS-A Property
。
您有几种方法来实现类层次结构和“is-a”关系。
正如您所建议的,使用类型鉴别器来显示这确实是哪个子类。当您经常需要生成各种子类的联合时,这很有效。如果您需要所有属性 - CaseProperty 和 QuoteProperty 的联合,那么这可以解决。
您不必依赖继承;您可以为每组关系设置不相交的表。 CaseProperty
和 QuoteProperty
。您还需要 CaseMessage
和 QuoteMessage
,以继续区分。
您可以在一个公共表中拥有公共特征,在一个单独的表中分离特征,并进行连接以重建单个对象。因此,您可能有一个 Property
表,其中包含所有属性的共同特征,以及 CaseProperty
和 QuoteProperty
以及 Property
的每个子类的独特特征。这类似于您提出的 Case
和 Quote
具有指向 Property
的外键的建议。
您可以将多态类层次结构展平为单个表,并使用类型鉴别器和 NULL。主表Property
具有Case
和Quote
的类型鉴别器。对于应该是 Quote
的行,Case
的属性为空。同样,Quote
的属性对于应该是 Case
的行被置空。
您的问题“[如何] 将 Message 表关联回 Case 和 Quote 表”源于一组多态子类。在这种情况下,最好的解决方案可能是这样。
Message
具有对Property
的 FK 引用。
Property
有一个类型鉴别器来区分Quote
和Case
。 Quote
和 Case
类定义都映射到 Property
,但依赖于类型鉴别器和(通常)不同的列集。
关键是Property
、CaseProperty
和QuoteProperty
的责任属于该类层次结构,而不是Message
。
【讨论】:
对不起,我没有把我的层次结构说得很清楚。一个 Property 有一个 List这就是服务的 DDD 概念的用武之地。每个具体类的存储库仅保留该实体,而不是相关对象。
所以你有 Property(),并且是你的 CaseProperty() 的基础:Property()。这个特殊实体是通过 CasePropertyService() 访问的。在这里,您可以对相关表执行 JOIN 等操作,以生成您的 CaseProperty() 特殊实体(这实际上不是 Case() 和 Property 本身,而是组合)。
OT:由于 .net 的限制,您不能继承多个类,这是我的工作。 DDD 旨在成为对您的领域的整体理解的指南。我经常把我的 DDD 大纲给朋友,让他们试着弄清楚它的作用/代表什么。如果它看起来很干净并且他们弄清楚了,它就是干净的。如果你的朋友看着它说:“我不知道你在这里坚持什么。”然后回到绘图板。
但是,使用任何 ORM 来持久化 DDD 对象(linq、EntityFramework 等)的存储存在一个问题。看看我的回答:
***: Question about Repositories and their Save methods for domain objects
关键是所有对象都必须在 ORM 的数据库中具有标识。因此,这有助于您规划数据库结构。
我最近不再使用 ORM 来控制直接访问,而只使用了一个干净的 DDD 层。我让我的存储库和服务控制对 DB 层的访问,并使用 Velocity 对我的对象进行实体缓存。这实际上非常适用于:1)DB 性能,但是您设计的最有效的是不使用直接 ORM 表示耦合到您的域对象,以及 2)您的域模型变得更加清晰,没有对值对象等的强制标识。免费!
【讨论】:
以上是关于如何让我的 SQL DB 与我的领域驱动设计相匹配的主要内容,如果未能解决你的问题,请参考以下文章