我们是否对事件存储执行查询?何时以及如何执行?

4

我对事件溯源很新,但据我所了解,当我们有一个命令用例时,在内存中实例化聚合体,从事件存储库中应用事件以使其处于正确的状态,进行适当的更改,然后将这些更改存储回事件存储库。我们还有一个读取模型存储,它最终会通过这些更改进行更新。

在我的情况下,我有一个CreateUserUseCase(这是一个命令用例),我想首先检查用户是否已经存在,以及用户名是否已经被使用。例如,像这样:

        const userAlreadyExists = await this.userRepo.exists(email);

        if (userAlreadyExists) {
           return new EmailAlreadyExistsError(email);
        }

        const alreadyCreatedUserByUserName = await this.userRepo
        .getUserByUserName(username);

        if (alreadyCreatedUserByUserName) {
           return new UsernameTakenError(username);
        }

        const user = new User(username, password, email);
        await this.userRepo.save(user);

因此,对于save方法,我将使用事件存储并将未提交的事件附加到其中。但是对于existsgetUserByUserName方法呢?一方面,我想进行特定的查询,因此可以使用我的读取模型存储获取所需的数据,但另一方面,这与CQRS存在矛盾。那么我们在这些情况下该怎么做?我们是否以某种方式对事件存储执行查询?以何种方式执行此操作?谢谢!
1个回答

1
CQRS不应被解释为“不要查询写模型”(因为为了处理命令而确定状态的过程涉及查询,这种限制是不可行的)。相反,应将其解释为“对于查询来说,使用不同的数据模型与用于处理更新意图的数据模型不同是完全可以接受的”。这种表述意味着,如果写模型适合特定查询,则可以对写模型执行查询。
事件溯源在某些使用方式下可以说是优化写入和读取的数据模型的极致,因此事件溯源模型使得除一小部分查询外,几乎所有查询都非常低效,需要某种形式的CQRS。
事件存储包含的查询设施通常很有限,但任何适合作为事件存储的东西都会包括一个复合查询,它相当于“为该实体提供最新快照,并为该快照之后的第一个n个事件(如果快照存在)或该实体的前n个事件(如果没有快照)”。该查询的结果对于“该实体是否已发布事件”这个问题是决定性的(除保留等因素外)。

谢谢您的回复!好的,我明白这不是“不查询写模型”的问题,但正如您所说,我想到的是查询“按ID给我前n个实体事件”。那么我们如何按电子邮件或用户名获取事件呢?我们会将所有潜在键(ID、电子邮件、用户名)的事件存储到事件存储中吗? - elli
这是一种方法,您可以这样做。您可以为电子邮件地址实体存储一个事件,其中包含“此电子邮件希望使用此用户名创建用户”:它并不意味着用户名存在,但这意味着(除非/直到被后续事件所否认,例如“原来该用户名已存在,因此此电子邮件不尝试使用该名称创建用户”,或者可能是“此电子邮件与该用户名解除关联”;您还可以有一个“是的,该用户名已创建,因此将此电子邮件视为该用户名的别名”事件)。 - Levi Ramsey
1
你的事件存储可能还支持标记事件,可以支持足够高的标记基数,以便你可以为用户帐户的电子邮件地址标记事件并按标记查询。 - Levi Ramsey
你好,Levi!我还想问一下,如果我们采用将事件保存到事件存储中的方法,并使用所有可能的键(id、电子邮件、用户名),那么当向事件存储追加事件时,我们是否需要以事务方式更新所有写模型(具有键:id、电子邮件、用户名)? - elli
1
它不一定需要是事务性的。如果电子邮件和用户名是事件的一部分,那么您可以拥有将按ID键入的事件投影为按电子邮件/用户名键入的事件的内容。 - Levi Ramsey

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