使用 Natural key 作为 DomainObject 或 GUID 的 ID + 自增领域驱动设计

using Natural key as the ID of DomainObject or GUID + auto-increment Domain Driven Design

我已经阅读了很多关于 DDD 的文章,并且注意到大多数在持久化到数据库时都使用 GUID 作为它们的 ID。他们说 GUID 可以很好地扩展,而自动递增 ID 在可扩展性方面是一个很大的禁忌。

我现在很困惑是使用 GUID 还是 auto-increment

基本上域名大约是 membership system (binary tree). (tracking of register members)

第一个要求是我们应该有一些在系统中唯一标识它们的东西(我们称之为Account No.)也许7digit。

然后新的Members可以被另一个Member注册。我们称之为推荐。

现在我打算做的是将 MemberId 的 GUID 类型作为 DomainObject Id 用作主键,用于连接、外键(在推荐时,referer_id 将是 GUID MemberId)。 AccountNo 将是一个自增列,也可能会通过 MAX() + 1 从存储库中获取。主要用于系统和链接中的搜索功能。

DomainObject 的 ID 是否应该对系统用户隐藏,因为它只是一种技术实现?

两者结合可以吗?数据库中的 GUID 为 row_id(代理键)。和(自然键)的自动递增?

是否可以从构造函数中排除 AccountNo 因为它无论如何都会自动递增?强制不变量的需要又如何呢?那么从存储库中获取下一个 ID 并在构造函数中包含 AccountNo 的方法是什么?

我是否应该坚持使用自动递增 ID 而忘记 GUID,删除 MemberId 并让 AccountNo 成为 DomainObject 的 ID?

注意:

我不是在构建下一个 facebook 之类的东西。

我只是想练习 DDD 的战术方面,学习如何在了解优缺点的情况下做出艰难的架构决策。

我只是想练习 DDD 的战略方面,以学习如何在了解其优缺点及其实施的情况下做出艰难的架构决策。

如果我们将通过会员注册制作 3 个场景:

它将如何影响决策?

技术栈:

你的问题可能太多了,无法给你一个完整的答案,因为ID设计并不简单,有很多方面。我可以推荐 Vaughn Vernon 的书 "Implementing DDD",它有一节专门介绍身份设计(第 5 章 "Entities" - 唯一身份)。

无论如何,我会尝试为您指明正确的方向,而不是背诵该章中的所有内容:-)。

你需要什么?

您已经提出了一些关于 ID 设计的问题,但您还有更多问题需要提出。只有这样,您才能决定 GUID、DB 生成的 ID 或仍然不同的 ID 是否合适。

  • ID 的域意义是什么?可能 ID 只是为了促进技术解决方案,但根本不是域的一部分?
  • ID是谁提供的?这可能是用户、应用程序、数据库甚至是另一个限界上下文。
  • 你什么时候需要身份证?在创建关联实体之前、创建时或仅在实体持久化时?

这些问题的答案将限制您可以使用的 ID 生成类型。

你应该知道什么

关于 ID 设计有一些规则。强烈推荐关注他们,以免以后搬起石头砸自己的脚:

  • 确保您的 ID 是唯一的。对于用户提供的 ID,这可能尤其令人担忧。您需要强制执行唯一性并为您的用户提供检测现有 ID 的可能性。对于应用程序生成的随机 ID 或数据库生成的 ID,这通常不是问题。
  • 确保您的 ID 稳定。永远不要更改实体的 ID!毕竟,ID 是您用来引用实体的东西。这样做的结果是您不应该将可能更改的内容作为您 ID 的一部分。例如。一个人的姓氏可能不是一个好的选择,因为当某人结婚时它可能会改变(并且由于非唯一性也可能有问题)。
  • 如果 ID 只是一个技术概念,请隐藏它们。从 DDD 的角度来看,将技术概念作为领域模型的一部分是错误的。

例子

以下是如何找到 ID 设计的示例:

允许延迟创建 ID(即通过持久性)意味着您在创建实体时确实拥有没有 ID 的实体。因此,如果您需要早期或即时 ID,则不能使用数据库生成的 ID(除非您同意联系数据库只是为了获取 ID)。

然后您可能会确定用户对 ID 不感兴趣,因此必须指定 ID 会很奇怪。这留下了应用程序生成的 ID。

对于应用程序生成的 ID,您需要确保 ID 是唯一的。这对于单实例应用程序来说是微不足道的,但一旦您创建具有多个应用程序实例的负载平衡设置时,问题可能会更多。这就是为什么很多人一开始就使用随机ID(比如GUID)的原因,这样在scale out的时候就不会钻牛角尖了。

如您所见,这个例子做了很多假设。没有对错之分,但有了上述问题,您应该能够做出明智的决定。

I just want to practice the Tactical side of DDD to learn how to make hard architectural decisions knowing their PROS and CONS.

你错了。你不能通过战术来学习战略。战术是实施战略的方法。但是你首先需要一个策略。

不管怎样,关于你的问题,很简单:使用 Guid。它有2个优点

  1. 全局标识符
  2. 可以从应用程序轻松生成。自动递增的 id 意味着复杂的服务或对数据库的依赖。不要让你的生活复杂化。

也应该使用自然id,比如AccountNo。但是,Guid 用于技术目的。自然键格式将来可能会改变,Guid 可以很容易地支持自然键多种格式。

作为一种实践,您的实体 ID 最好是一个值对象(即使它只是 Guid)。您也可以将 guid 合并到 AccountNo 中,一个 VO 不必只有一个值。例如,在我当前的应用程序中,我有一个 ProjectId(Guid organization,Guid idValue)ProjectAssetId(Guid organization,Guid projectId,Guid idValue)

They say that GUID scales well and auto incrementing ID's are a big no-no when it comes to scalability.

自动递增整数的主要可伸缩性问题在于插入:由于新值 "bunch up" 一起位于值范围的上限,它们往往会达到相同的 "side" B 树(可能是同一个叶节点),导致锁定并降低并发性。

每分钟插入一次,您根本看不到任何这些,因此请根据其他条件选择您的密钥。正如您所描述的那样,自动递增整数在您的场景中会很好地服务......它们更轻量级并且可能比 GUID 表现更好。如果 32 位变体不够宽,就使用 64 位。


顺便说一句,自动递增整数不是通过查询 MAX() + 1 生成的 - 所有 DBMS 都有自己的高性能版本 "sequence generator",您可以直接使用。

您还可以 return 将生成的值直接发送给客户端,而无需额外的往返(例如 Oracle 的 RETURNING 或 SQL 服务器的 OUTPUT 子句)。不幸的是,ORM 并不总是能很好地处理...

请允许我为自增的想法辩护。的确,GUID 更具可扩展性。但是任何设计师在某些时候都应该问的一个重要问题是 "How much scalability do I need?"

答案很少"As much as possible!"在现实世界中,任何事物都是有限度的。我们的数据库模拟真实世界。

例如,如果您正在与人(用户、客户、学生等)打交道,一个 64 位整数可以包含地球上所有人口的许多倍。很多次。我们在这里谈论 "population of the galactic empire"。使用 bigint,您几乎可以唯一地识别宇宙中的每个原子。

不要偷懒,尤其是在设计阶段。设计一个合理的安全边际,然后继续。任何更多的东西都会不必要地增加系统的复杂性和 "friction"。

我在这个领域的经验现在可以用几十年来衡量——其中有好几年。在那段时间里,我从来没有使用 GUID 来实现可伸缩性。我发现 GUID 的唯一实际用途是在不同的(通常是远程的)数据库中创建实体,然后必须将这些数据库合并到一个中央数据库中。 GUID 消除了(从统计学上讲,即)合并期间发生冲突的可能性。