如何确保更改域的数据完整性
How to ensure data integrity with domain that change
我正在从事一个应用 DDD 原则的项目。
为了确保域完整性,我在创建时验证每个域模型(实体或值对象)。
用户实体示例:
class User {
constructor(opts) {
this.email = opts.email;
this.password = opts.password;
this.validate();
}
validate() {
if(typeof this.email !== 'string') {
throw new Error('email is invalid');
}
if(typeof this.password !== 'string') {
throw new Error('password is invalid');
}
}
}
validate
方法是验证的愚蠢实现(我知道我应该使用 Regex 验证电子邮件并且我以最有效的方式处理错误)。
然后使用 userRepository
模块保留此模型。
现在,假设我想向我的用户模型添加一个新的 属性 username
,我的 validate
方法将如下所示:
validate() {
if(typeof this.email !== 'string') {
throw new Error('email is invalid');
}
if(typeof this.password !== 'string') {
throw new Error('password is invalid');
}
if(typeof this.username !== 'string') {
throw new Error('username is invalid');
}
}
问题是存储的旧用户模型没有现在需要的用户名 属性。因此,当我从数据库中获取数据并尝试构建模型时,它会抛出一个错误。
为了解决这个问题,我看到了多种解决方案(但 none 对我来说似乎不错):
- 在用户存储库中创建一个反腐败层(如果未定义则创建默认用户名)
- 在我的域模型中允许不变(不需要用户名)
- 使用基于域更改更新数据库实体的 cron 服务(再次设置默认用户名)
The problem is that old user models stored will not have the username property which is now required.
是的,这是个问题。
我是这样想的——你的域模型的持久副本是一个消息,由你的域模型实例运行过去发送到你的域模型的一个实例 运行 以后。
如果您希望这些消息兼容,则需要在消息架构设计中接受某些约束。
其中一个限制是您不能向现有消息类型添加新的必需 字段。
添加可选字段就可以了,因为不关心的系统可以忽略可选字段,而关心的系统可以提供默认值失踪。
但是如果您需要添加新的必填字段,那么您创建一条新消息。
事件溯源社区必须非常担心这类事情(事件是消息); Greg Young 写了 Versioning in an Event Sourced System,其中对消息的版本控制有很好的教训。
To fix this problem I see multiple solutions (but none seems good to me)
我同意,这些都有些糟糕 - 从某种意义上说,它们都引入了一种机制来派生 "default" 存在 none 的用户名。在这种情况下,该字段实际上是可选的;那么为什么声称它是必需的?
如果该字段不是必需的,但您想停止接受不包含该字段的新数据——您可能想对数据输入代码路径进行新的验证。也就是说,您可以创建一个 new API 带有需要该字段的消息,验证这些消息,然后使用带有可选字段的领域模型来存储和获取数据。
So adding a new required field is an anti-pattern in DDD
添加新的必填字段是消息传递中的反模式; DDD与它无关。
您不应期望能够以向后兼容的方式将必填字段添加到现有架构。相反,您 扩展 消息模式,方法是引入一个新消息,其中该字段是必需的。
I thought applying DDD principles help to handle the business logic complexity and also help to design evoluting software and evoluting domain models
确实如此,但它不是魔法。如果您的新模型不能向后兼容旧模型,那么您将不得不以某种方式应对这种变化
你可能会宣告破产,然后干脆忘记所有以前的历史。
您可能会将现有数据迁移到新数据模型。
您可以并行维护两个不同的数据模型。
换句话说,向后兼容性是您在设计解决方案时应该考虑的长期问题。
我正在从事一个应用 DDD 原则的项目。
为了确保域完整性,我在创建时验证每个域模型(实体或值对象)。
用户实体示例:
class User {
constructor(opts) {
this.email = opts.email;
this.password = opts.password;
this.validate();
}
validate() {
if(typeof this.email !== 'string') {
throw new Error('email is invalid');
}
if(typeof this.password !== 'string') {
throw new Error('password is invalid');
}
}
}
validate
方法是验证的愚蠢实现(我知道我应该使用 Regex 验证电子邮件并且我以最有效的方式处理错误)。
然后使用 userRepository
模块保留此模型。
现在,假设我想向我的用户模型添加一个新的 属性 username
,我的 validate
方法将如下所示:
validate() {
if(typeof this.email !== 'string') {
throw new Error('email is invalid');
}
if(typeof this.password !== 'string') {
throw new Error('password is invalid');
}
if(typeof this.username !== 'string') {
throw new Error('username is invalid');
}
}
问题是存储的旧用户模型没有现在需要的用户名 属性。因此,当我从数据库中获取数据并尝试构建模型时,它会抛出一个错误。
为了解决这个问题,我看到了多种解决方案(但 none 对我来说似乎不错):
- 在用户存储库中创建一个反腐败层(如果未定义则创建默认用户名)
- 在我的域模型中允许不变(不需要用户名)
- 使用基于域更改更新数据库实体的 cron 服务(再次设置默认用户名)
The problem is that old user models stored will not have the username property which is now required.
是的,这是个问题。
我是这样想的——你的域模型的持久副本是一个消息,由你的域模型实例运行过去发送到你的域模型的一个实例 运行 以后。
如果您希望这些消息兼容,则需要在消息架构设计中接受某些约束。
其中一个限制是您不能向现有消息类型添加新的必需 字段。
添加可选字段就可以了,因为不关心的系统可以忽略可选字段,而关心的系统可以提供默认值失踪。
但是如果您需要添加新的必填字段,那么您创建一条新消息。
事件溯源社区必须非常担心这类事情(事件是消息); Greg Young 写了 Versioning in an Event Sourced System,其中对消息的版本控制有很好的教训。
To fix this problem I see multiple solutions (but none seems good to me)
我同意,这些都有些糟糕 - 从某种意义上说,它们都引入了一种机制来派生 "default" 存在 none 的用户名。在这种情况下,该字段实际上是可选的;那么为什么声称它是必需的?
如果该字段不是必需的,但您想停止接受不包含该字段的新数据——您可能想对数据输入代码路径进行新的验证。也就是说,您可以创建一个 new API 带有需要该字段的消息,验证这些消息,然后使用带有可选字段的领域模型来存储和获取数据。
So adding a new required field is an anti-pattern in DDD
添加新的必填字段是消息传递中的反模式; DDD与它无关。
您不应期望能够以向后兼容的方式将必填字段添加到现有架构。相反,您 扩展 消息模式,方法是引入一个新消息,其中该字段是必需的。
I thought applying DDD principles help to handle the business logic complexity and also help to design evoluting software and evoluting domain models
确实如此,但它不是魔法。如果您的新模型不能向后兼容旧模型,那么您将不得不以某种方式应对这种变化
你可能会宣告破产,然后干脆忘记所有以前的历史。 您可能会将现有数据迁移到新数据模型。 您可以并行维护两个不同的数据模型。
换句话说,向后兼容性是您在设计解决方案时应该考虑的长期问题。