领域模型和数据库模型有什么不同?

11

我理解DDD的概念,但在实践中有些困惑。

我正在使用C#,SQL Server和EF。 根据我的数据库模式,持久化模型看起来与聚合不同。为了定义干净、漂亮的聚合、实体和值对象,我的领域模型看起来与数据库模型不同。

此外,如果我试图合并这两者,那么我会以技术为基础而不是领域来设计我的领域模型。

也许更具体的例子是:

  • 如果ID仅用于数据库,那么我需要在实体中添加ID字段吗?

  • 在Many2Many关系的情况下,也可能会变得棘手。

这两个模型是否不同,并且存储库的实现必须将db模型转换为领域模型,还是我应该使领域模型与EF使用的模型相同?


这可能更适合软件工程。它有点宽泛,那些孩子们不介意在主观的SE话题上大放厥词。 - TheGeneral
1
https://blog.sapiensworks.com/post/2012/04/07/Just-Stop-It!-The-Domain-Model-Is-Not-The-Persistence-Model.aspx - jlvaquero
1个回答

11

当你开始使用DDD时,这是一个常见的问题。

领域模型是一种抽象。它不应关注你使用的技术,也不代表数据库表、文档、类、函数等等。

领域模型表示概念、依赖和概念之间的相互作用。

技术用于实现领域模型。

可以使用不同的技术来实现相同的领域模型。

虽然我们想要在领域模型上拥有自由度,但实践中我们需要使用技术进行实现,有时这可能会对其产生影响。

当然,你可以放弃所有的框架和库,自己制定解决方案,使实现更加容易。即使这样做,你仍然需要考虑你的语言,例如C#、Java、Ruby等以及提供给你的工具。

以下是一个例子:

假设我们开发了一个租车系统。一个人可以租一辆车。在我们的领域中,我们有一个人所拥有的帐户的概念、汽车和汽车租赁的概念。

这就是你的领域模型。目前为止,我们不关心语言、数据库或任何东西。

现在来看领域模型的实现。

我们将使用C#。现在需要决定数据库。如果我们决定使用SQL,我们可以使用RDBMS的良好能力来进行连接,因此我们可以通过使用整数ID来实现它:

public class Account {
    public int ID { get; private set; }
    // other stuff
}

public class Car {
    public int ID { get; private set; }
    // other stuff
}

public class CarRental {
    public int AccountID { get; private set; }
    public int CarID { get; private set; }
    // other stuff
}

另一方面,我们可能认为整数ID不好,因为如果你必须与其他系统和数据库移动等一起工作,可能会发生碰撞,因此我们决定思考并使用电子邮件作为帐户的唯一标识符,使用车牌号作为汽车的唯一标识符:

public class Account {
    public Email Email { get; private set; }
    // other stuff
}

public class Car {
    public LicensePlace LicensePlace { get; private set; }
    // other stuff
}

public class CarRental {
    public Email AccountEmail { get; private set; }
    public LicensePlace CarLicensePlace { get; private set; }
    // other stuff
}

如果我们遵循DDD并正确将您的实体划分为聚合,则不需要在它们之间执行联接,因为它们不应在同一事务中加载或更改。

另一方面,我们可能希望在同一个数据库上构建不同的模型(也许是读模型,如果我们使用CQRS,或者我们可能需要提取报告),使用第二种方法可能会使这更加困难,因为我们失去了执行联接的能力。

另一方面,如果我们使用像MongoDB这样的NoSQL数据库,我们就无法执行联接。因此,使用整数ID不会给我们带来任何价值。

即使我们决定使用SQL数据库并且不使用整数ID,我们仍然可以使用域事件并使用它们构建其他模型和报告。

如果我们有一个分布式系统,使用联接也将无法工作,因此使用整数ID可能根本没有带来任何价值。

有时候,利用技术的能力使某些事情变得更容易,但我们让它损害了我们的系统领域模型

我们最终建造出的系统从技术角度来看非常强大,但它们不能做它们应该做的事情,这使它们变得无用。

一个无用的系统是无用的,无论它的实现有多好。

如果您还没有阅读过DDD 书籍,请阅读它。Eric Evans谈到了如何利用技术来帮助您或全程与您作斗争。Eric Evans还讨论了DDD如何允许自由实现,以便您不必与您的技术作斗争。

我们倾向于一直考虑持久性,这是另一件事。确实,我们大部分时间都在持久化事物,但这并不意味着领域模型是要持久化到数据库中的东西。

当我开始编程时,我从计算机图形学和建模应用程序(如3DsMax和Maya)开始。当我开始编写使用神圣的数据库的应用程序时,我真的感到很奇怪,人们不关心也不了解他们的领域,只是持久化它们并使它们工作,所有他们谈论的都是技术。

如果您热衷于计算机图形学,如果您不了解数学,则无法编写一行代码。因此,您开始学习数学。在您掌握一些知识之后,就可以编写代码了。

以游戏为例,你可能会设计一个模拟物理的物理引擎。在这个模型中,你会有像工作功率重力加速度等概念。它们不需要被持久化到数据库中。例如,你会持久化你的玩家重量,让物理引擎知道重力如何影响它,但仍然不需要将功率持久化到数据库中。这仍然是一个域模型功率工作等是函数,不是聚合实体。它们仍然是你的域模型的一部分。

假设你想建立一个物理引擎。事实上,如果你想建立一个物理引擎,你必须了解物理学。物理学很复杂。即使你是一个非常擅长EF或SQL编程的人,这也不会帮助你构建物理引擎。了解物理领域并能够制作其域模型,然后再实现是关键。

如果你想真正感受域模型和实现之间的区别,请查看这个链接。在你开始任何实现之前,这个领域可能会让你大吃一惊。

同时,请查看这篇有关使用DDD建模实体的文章。

编辑

这篇文章解释了NHibernate和EntityFramework在领域建模方面的不同之处。


嗨@expandable,非常感谢您的解释。我基本上明白了。 我认为这篇最后的文章正是我在寻找的。我会全文阅读它。 那么,我得到的答案是,域模型实现也可以用于EF中的DBContext。是这样吗? - ClaudiuSocaci
2
ORM类通常比领域类限制要少得多。当使用领域模型作为ORM模型或反之亦然时,通常会发现领域模型最终变得相当贫乏。ORM喜欢访问所有数据,封装性被忽略,留下的更像是DTO而不是领域类。在进行领域驱动设计时,我尽量避免使用ORM,但过去我不得不使用EF,然后在存储库中将EF对象(实体)与我的领域对象映射。 - Eben Roux
啊哈,现在我明白了。我花了一些时间看了一些其他的例子,就像你说的那样,最好将它们分开以保持清洁。但这并不意味着如果模型很简单,你不能将其作为EF实体使用。我对于在EF中拥有实体和一个独立的领域模型类的问题是,现在,就像你所说的,你需要在某个地方映射这两个。我们为此付出了很多代价。维护另一个层和映射器并不是很好,我想说。 - ClaudiuSocaci
是的,您可以将EF类用作实体和聚合。ORM越来越适合与良好的领域模型一起使用。不幸的是,有时更复杂的事情更难以处理,但如果您的模型允许,则可以直接使用EF而无需另一层。在我正在工作的应用程序中,我们无法使EF按照我们想要的方式运行,因此我们必须拥有两个类,即EF实体和我们的领域模型类,并且我们的存储库在它们之间进行映射。我们付出了代价,但由于直接使用EF是一场噩梦,所以这是值得的。在其他情况下,它可以正常工作。 - expandable

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