CQRS sem DDD e sem (ou com?) ES - o que é modelo de gravação e o que é modelo de leitura?

11

Pelo que entendi, a grande idéia por trás do CQRS é ter dois modelos de dados diferentes para manipular comandos e consultas. Eles são chamados de "modelo de gravação" e "modelo de leitura".

Vamos considerar um exemplo de clone de aplicativo do Twitter. Aqui estão os comandos:

  • Os usuários podem se registrar. CreateUserCommand(string username)emiteUserCreatedEvent
  • Os usuários podem seguir outros usuários. FollowUserCommand(int userAId, int userBId)emiteUserFollowedEvent
  • Os usuários podem criar postagens. CreatePostCommand(int userId, string text)emitePostCreatedEvent

Embora eu use o termo "evento" acima, não me refiro a eventos 'sourcing de eventos'. Quero dizer apenas sinais que acionam atualizações de modelo de leitura. Eu não tenho uma loja de eventos e até agora quero me concentrar no CQRS.

E aqui estão as perguntas:

  • Um usuário precisa ver a lista de suas postagens. GetPostsQuery(int userId)
  • Um usuário precisa ver a lista de seus seguidores. GetFollowersQuery(int userId)
  • Um usuário precisa ver a lista de usuários a seguir. GetFollowedUsersQuery(int userId)
  • Um usuário precisa ver o "feed de amigos" - um log de todas as atividades de seus amigos ("seu amigo John acabou de criar uma nova postagem"). GetFriedFeedRecordsQuery(int userId)

Para lidar CreateUserCommand, preciso saber se esse usuário já existe. Portanto, neste momento eu sei que meu modelo de gravação deve ter uma lista de todos os usuários.

Para lidar FollowUserCommand, preciso saber se o usuário A já segue o usuárioB ou não. Neste ponto, quero que meu modelo de gravação tenha uma lista de todas as conexões usuário-usuário-usuário.

E, finalmente, para lidar com CreatePostCommandisso, acho que não preciso de mais nada, porque não tenho comandos como UpdatePostCommand. Se eu os tivesse, precisaria garantir que a postagem exista, portanto, precisaria de uma lista de todas as postagens. Mas como não tenho esse requisito, não preciso rastrear todas as postagens.

Pergunta # 1 : é realmente correto usar o termo "modelo de gravação" da maneira que eu o uso? Ou "modelo de gravação" sempre significa "armazenamento de eventos" no caso de ES? Em caso afirmativo, existe algum tipo de separação entre os dados necessários para manipular comandos e os dados necessários para manipular consultas?

Para lidar GetPostsQuery, eu precisaria de uma lista de todas as postagens. Isso significa que meu modelo de leitura deve ter uma lista de todas as postagens. Vou manter esse modelo ouvindo PostCreatedEvent.

Para lidar com ambos GetFollowersQuerye GetFollowedUsersQuery, eu precisaria de uma lista de todas as conexões entre usuários. Para manter esse modelo, vou ouvir UserFollowedEvent. Aqui está uma pergunta 2 : é praticamente bom se eu usar a lista de conexões do modelo de gravação aqui? Ou devo criar melhor um modelo de leitura separado, porque no futuro posso precisar de mais detalhes do que o modelo de gravação?

Finalmente, para lidar, GetFriendFeedRecordsQueryeu precisaria:

  • Ouvir UserFollowedEvent
  • Ouvir PostCreatedEvent
  • Saiba quais usuários seguem quais outros usuários

Se o usuário A seguir o usuário B e o usuário B começar a seguir o usuário C, os seguintes registros deverão aparecer:

  • Para o usuário A: "Seu amigo usuário B acabou de começar a seguir o usuário C"
  • Para o usuário B: "Você começou a seguir o usuário C"
  • Para o usuário C: "O usuário B agora está seguindo você"

Aqui está a pergunta nº 3 : qual modelo devo usar para obter a lista de conexões? Devo usar o modelo de gravação? Devo usar o modelo de leitura - GetFollowersQuery/ GetFollowedUsersQuery? Ou devo fazer o GetFriendFeedRecordsQuerypróprio modelo manipular UserFollowedEvente manter sua própria lista de todas as conexões?

Andrey Agibalov
fonte
Lembre-se de que, nos sistemas CQRS, o modelo de dados de consulta e o modelo de dados de comando podem estar consumindo dados de diferentes bases de dados. E ambos os modelos podem estar vivendo independentemente um do outro (aplicativos diferentes). Dito isto, a resposta é "depende" (como de costume). Pode interessar
Laiv

Respostas:

7

Greg Young (2010)

CQRS é simplesmente a criação de dois objetos onde anteriormente havia apenas um.

Se você pensa em termos de separação de consultas por comandos de Bertrand Meyer , pode pensar no modelo como tendo duas interfaces distintas, uma que suporta comandos e outra que suporta consultas.

interface IChangeTheModel {
    void createUser(string username)
    void followUser(int userAId, int userBId)
    void createPost(int userId, string text)
}

interface IDontChangeTheModel {
    Iterable<Post> getPosts(int userId)
    Iterable<Follower> getFollowers(int userId)
    Iterable<FollowedUser> getFollowedUsers(int userId)
}

class TheModel implements IChangeTheModel, IDontChangeTheModel {
    // ...
}

A visão de Greg Young era que você poderia separar isso em dois objetos separados

class WriteModel implements IChangeTheModel { ... }
class ReadModel  implements IDontChangeTheModel {...}

Depois de separar os objetos, agora você tem a opção de separar as estruturas de dados que mantêm o estado do objeto na memória, para otimizar para cada caso; ou armazene / persista o estado de leitura separadamente do estado de gravação.

Pergunta # 1: é realmente correto usar o termo "modelo de gravação" da maneira que eu o uso? Ou "modelo de gravação" sempre significa "armazenamento de eventos" no caso de ES? Em caso afirmativo, existe algum tipo de separação entre os dados necessários para manipular comandos e os dados necessários para manipular consultas?

O termo WriteModel é geralmente entendido como a representação mutável do modelo (ou seja: o objeto, não o armazenamento de persistência).

TL; DR: Eu acho que seu uso é bom.

Aqui está uma pergunta 2: é praticamente bom se eu usar a lista de conexões do modelo de gravação aqui?

Isso é "bom" - ish. Conceitualmente, não há nada de errado com o modelo de leitura e gravação que compartilha as mesmas estruturas.

Na prática, como as gravações no modelo geralmente não são atômicas, há um problema potencial quando um encadeamento está tentando modificar o estado do modelo enquanto um segundo encadeamento está tentando lê-lo.

Aqui está a pergunta nº 3: qual modelo devo usar para obter a lista de conexões? Devo usar o modelo de gravação? Devo usar o modelo de leitura - GetFollowersQuery / GetFollowedUsersQuery? Ou devo fazer o próprio modelo GetFriendFeedRecordsQuery manipular o UserFollowedEvent e manter sua própria lista de todas as conexões?

Usar o modelo de gravação é a resposta errada.

Escrever vários modelos de leitura, nos quais cada um deles é ajustado para um caso de uso específico, é totalmente razoável. Dizemos "o modelo de leitura", mas entende-se que pode haver muitos modelos de leitura, cada um deles otimizado para um caso de uso específico e não implementa os casos em que não faz sentido.

Por exemplo, você pode optar por usar um armazenamento de valores-chave para dar suporte a algumas consultas e um banco de dados gráfico para outras consultas, ou um banco de dados relacional em que esse modelo de consulta faça sentido. Cavalos para cursos.

Em sua circunstância específica, onde você ainda está aprendendo o padrão, minha sugestão seria manter seu design "simples" - tenha um modelo de leitura que não compartilhe a estrutura de dados do modelo de gravação.

VoiceOfUnreason
fonte
1

Gostaria de encorajá-lo a considerar que você tem um modelo de dados conceitual.

Em seguida, o modelo de gravação é uma materialização desse modelo de dados que é otimizado para atualizações transacionais. Às vezes, isso significa um banco de dados relacional normalizado.

O modelo de leitura é uma materialização desse mesmo modelo de dados otimizado para executar as consultas que seu (s) aplicativo (s) precisa (s). Ainda pode ser um banco de dados relacional, embora propositalmente desnormalizado para lidar com consultas com menos junções.


(# 1) Para o CQRS, o modelo de gravação não precisa ser um armazenamento de eventos.

(# 2) Eu não esperaria que o modelo de leitura armazene nada que não esteja no modelo de gravação, porque, no CQRS, ninguém atualiza o modelo de leitura, exceto o mecanismo de encaminhamento que mantém o modelo de leitura sincronizado com as alterações no escreva modelo.

(# 3) No CQRS, as consultas devem ir contra o modelo de leitura. Você pode fazer diferente: tudo bem, apenas não seguindo o CQRS.


Em resumo, existe apenas um modelo de dados conceitual. O CQRS separa a rede e os recursos de comando da rede e dos recursos de consulta. Dado que a separação, o modelo de gravação e o modelo de leitura podem ser hospedados usando tecnologias muito diferentes como uma otimização de desempenho.

Erik Eidt
fonte