何时以及如何为 DDD 中的实体分配唯一 ID?
When and how to assign unique id to an entity in DDD?
最好的例子是需要持久化的 User
实体。我有以下候选人为用户分配唯一标识符:
- 分配后端提供的密钥(NDB、MySQL 等)。
- 通过某些服务(如系统时钟)手工制作唯一标识符。
- emailId 等属性。
举一个简单的详细视图的例子,我们经常有一个用户的详细显示,如some/path/users/{user_id}
,如果我们将emailId作为唯一id,那么用户可能会更改其电子邮件id有一天打破它。
识别同一实体的更好方法是什么?
已命名 UUID。
UUID,因为它为标识符提供了一个很好的可预测结构,而不会引入任何语义含义(例如您的电子邮件 ID 示例)。想想surrogate key.
Named UUID,因为你希望生成的id是确定性的。确定性意味着可重现:您可以将系统移至测试环境,并重放命令以检查结果。
它还为您提供了一种检测重复工作的额外方法 - 如果重复创建用户命令(例如:用户两次发布相同的 Web 表单),您的系统会发生什么情况。您可以通过多种方式在中间层中防止这种情况发生,但是在持久层(也称为记录系统)中覆盖这种情况的一种真正简单的方法是对 id 施加唯一性约束。因为 运行 第二次执行该命令会生成具有相同 ID 的 "new" 用户实体,持久层将反对复制,您可以从那里处理事情。
因此,即使所有中间保护层在重复命令之间的间隔期间重新启动,您也可以获得幂等命令处理。
命名 UUID 为您提供这些属性;例如,您可以根据实体类型的标识符和命令的 ID 构建 uuid(重复的命令在重新发送时将具有相同的 ID)。
如果您保证 属性 永远不会被分配给其他人,您可以使用用户的临时属性(如电子邮件地址)作为您命名的 uuid 的种子的一部分。您确定 vivek@whosebug.com 不会分配给其他用户吗?那就不好用了。
如果命令重复,后端键分配将不会检测到冲突 - 您将需要依赖一些其他状态位来检测冲突。
系统时钟不是一个好的选择,因为它使复制相同的 id 变得困难。如果您可以在测试环境中重现对本地时钟的更新,则系统时钟的本地副本可以工作。但如果时间还不是您的域模型的一部分,那将是您不想要的一堆额外工作。
- https://www.ietf.org/rfc/rfc4122.txt(第 4.3 节)
- http://www.rfc-editor.org/errata_search.php?rfc=4122&eid=1352(规范中示例的勘误表)
- Generating v5 UUID. What is name and namespace?
我同意@VoiceOfUnreason 但只是部分同意。我们都知道 UUID 很难拼写和跟踪。所有使用增量和有意义的 UUID 的方法只能解决部分问题。
正在使用创建方已经可用的一些 ID 创建聚合。尽管可以在不涉及任何外部组件的情况下生成 UUID,但这不是唯一的解决方案。使用像 Twitter Snowflake(已停用)这样的外部身份提供者也是一种选择。
创建非常简单和可靠的身份提供者并不是很复杂,可以通过给定聚合类型名称 return 增加 long 值。
当然,它增加了复杂性,只有在需要生成连续的唯一数值时才合理。此服务的弹性变得非常重要,需要谨慎处理。但它可以被视为任何其他关键基础设施组件,我们知道每个系统都有相当多的组件。
最好的例子是需要持久化的 User
实体。我有以下候选人为用户分配唯一标识符:
- 分配后端提供的密钥(NDB、MySQL 等)。
- 通过某些服务(如系统时钟)手工制作唯一标识符。
- emailId 等属性。
举一个简单的详细视图的例子,我们经常有一个用户的详细显示,如some/path/users/{user_id}
,如果我们将emailId作为唯一id,那么用户可能会更改其电子邮件id有一天打破它。
识别同一实体的更好方法是什么?
已命名 UUID。
UUID,因为它为标识符提供了一个很好的可预测结构,而不会引入任何语义含义(例如您的电子邮件 ID 示例)。想想surrogate key.
Named UUID,因为你希望生成的id是确定性的。确定性意味着可重现:您可以将系统移至测试环境,并重放命令以检查结果。
它还为您提供了一种检测重复工作的额外方法 - 如果重复创建用户命令(例如:用户两次发布相同的 Web 表单),您的系统会发生什么情况。您可以通过多种方式在中间层中防止这种情况发生,但是在持久层(也称为记录系统)中覆盖这种情况的一种真正简单的方法是对 id 施加唯一性约束。因为 运行 第二次执行该命令会生成具有相同 ID 的 "new" 用户实体,持久层将反对复制,您可以从那里处理事情。
因此,即使所有中间保护层在重复命令之间的间隔期间重新启动,您也可以获得幂等命令处理。
命名 UUID 为您提供这些属性;例如,您可以根据实体类型的标识符和命令的 ID 构建 uuid(重复的命令在重新发送时将具有相同的 ID)。
如果您保证 属性 永远不会被分配给其他人,您可以使用用户的临时属性(如电子邮件地址)作为您命名的 uuid 的种子的一部分。您确定 vivek@whosebug.com 不会分配给其他用户吗?那就不好用了。
如果命令重复,后端键分配将不会检测到冲突 - 您将需要依赖一些其他状态位来检测冲突。
系统时钟不是一个好的选择,因为它使复制相同的 id 变得困难。如果您可以在测试环境中重现对本地时钟的更新,则系统时钟的本地副本可以工作。但如果时间还不是您的域模型的一部分,那将是您不想要的一堆额外工作。
- https://www.ietf.org/rfc/rfc4122.txt(第 4.3 节)
- http://www.rfc-editor.org/errata_search.php?rfc=4122&eid=1352(规范中示例的勘误表)
- Generating v5 UUID. What is name and namespace?
我同意@VoiceOfUnreason 但只是部分同意。我们都知道 UUID 很难拼写和跟踪。所有使用增量和有意义的 UUID 的方法只能解决部分问题。
正在使用创建方已经可用的一些 ID 创建聚合。尽管可以在不涉及任何外部组件的情况下生成 UUID,但这不是唯一的解决方案。使用像 Twitter Snowflake(已停用)这样的外部身份提供者也是一种选择。
创建非常简单和可靠的身份提供者并不是很复杂,可以通过给定聚合类型名称 return 增加 long 值。
当然,它增加了复杂性,只有在需要生成连续的唯一数值时才合理。此服务的弹性变得非常重要,需要谨慎处理。但它可以被视为任何其他关键基础设施组件,我们知道每个系统都有相当多的组件。