如何确保更改域的数据完整性

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 对我来说似乎不错):

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

确实如此,但它不是魔法。如果您的新模型不能向后兼容旧模型,那么您将不得不以某种方式应对这种变化

你可能会宣告破产,然后干脆忘记所有以前的历史。 您可能会将现有数据迁移到新数据模型。 您可以并行维护两个不同的数据模型。

换句话说,向后兼容性是您在设计解决方案时应该考虑的长期问题。