CQRS senza DDD e senza (o con?) ES: cos'è il modello di scrittura e cos'è il modello di lettura?


11

A quanto ho capito, l'idea alla base di CQRS è avere 2 diversi modelli di dati per la gestione di comandi e query. Questi sono chiamati "modello di scrittura" e "modello di lettura".

Consideriamo un esempio del clone dell'applicazione Twitter. Ecco i comandi:

  • Gli utenti possono registrarsi. CreateUserCommand(string username)emetteUserCreatedEvent
  • Gli utenti possono seguire altri utenti. FollowUserCommand(int userAId, int userBId)emetteUserFollowedEvent
  • Gli utenti possono creare post. CreatePostCommand(int userId, string text)emettePostCreatedEvent

Mentre uso il termine "evento" sopra, non intendo eventi di "sourcing di eventi". Intendo solo segnali che attivano gli aggiornamenti del modello di lettura. Non ho un negozio di eventi e finora voglio concentrarmi sullo stesso CQRS.

Ed ecco le domande:

  • Un utente deve vedere l'elenco dei suoi post. GetPostsQuery(int userId)
  • Un utente deve vedere l'elenco dei suoi follower. GetFollowersQuery(int userId)
  • Un utente deve vedere l'elenco degli utenti che segue. GetFollowedUsersQuery(int userId)
  • Un utente deve vedere il "feed degli amici", un registro di tutte le attività dei suoi amici ("il tuo amico John ha appena creato un nuovo post"). GetFriedFeedRecordsQuery(int userId)

Per gestire CreateUserCommandho bisogno di sapere se un tale utente esiste già. Quindi, a questo punto, so che il mio modello di scrittura dovrebbe avere un elenco di tutti gli utenti.

Per gestire FollowUserCommandho bisogno di sapere se userA segue già userB oppure no. A questo punto voglio che il mio modello di scrittura abbia un elenco di tutte le connessioni user-follow-user.

E infine, per gestire CreatePostCommandnon penso di aver bisogno di nient'altro, perché non ho comandi simili UpdatePostCommand. Se avessi quelle, avrei bisogno di assicurarmi che quel post esistesse, quindi avrei bisogno di un elenco di tutti i post. Ma poiché non ho questo requisito, non ho bisogno di tenere traccia di tutti i post.

Domanda n. 1 : è effettivamente corretto usare il termine "modello di scrittura" nel modo in cui lo uso? Oppure "scrivi modello" significa sempre "negozio di eventi" in caso di ES? In tal caso, esiste un tipo di separazione tra i dati di cui ho bisogno per gestire i comandi e quelli di cui ho bisogno per gestire le query?

Per gestire GetPostsQuery, avrei bisogno di un elenco di tutti i post. Ciò significa che il mio modello di lettura dovrebbe avere un elenco di tutti i post. Manterrò questo modello ascoltando PostCreatedEvent.

Per gestire entrambi GetFollowersQuerye GetFollowedUsersQuery, avrei bisogno di un elenco di tutte le connessioni tra utenti. Per mantenere questo modello ho intenzione di ascoltare UserFollowedEvent. Ecco una domanda n. 2 : è praticamente OK se uso l'elenco delle connessioni del modello di scrittura qui? O dovrei creare un modello di lettura separato, perché in futuro potrei aver bisogno di avere più dettagli rispetto al modello di scrittura?

Infine, per gestire GetFriendFeedRecordsQueryavrei bisogno di:

  • Ascoltare UserFollowedEvent
  • Ascoltare PostCreatedEvent
  • Scopri quali utenti seguono quali altri utenti

Se l'utente A segue l'utente B e l'utente B inizia a seguire l'utente C, devono essere visualizzati i seguenti record:

  • Per l'utente A: "Il tuo amico utente B ha appena iniziato a seguire l'utente C"
  • Per l'utente B: "Hai appena iniziato a seguire l'utente C"
  • Per l'utente C: "L'utente B ora ti sta seguendo"

Ecco la domanda n. 3 : quale modello dovrei usare per ottenere l'elenco delle connessioni? Dovrei usare il modello di scrittura? Dovrei usare read model - GetFollowersQuery/ GetFollowedUsersQuery? O dovrei fare in modo che il GetFriendFeedRecordsQuerymodello stesso gestisca UserFollowedEvente mantenga il proprio elenco di tutte le connessioni?


Tenere presente che nei sistemi CQRS, il modello di dati Query e il modello di dati Command potrebbero consumare dati da basi di dati diverse. E entrambi i modelli potrebbero vivere indipendentemente (app diverse). Detto questo, la risposta è "dipende" (come al solito). Potrebbe interessarti
Laiv

Risposte:


7

Greg Young (2010)

CQRS è semplicemente la creazione di due oggetti in cui prima ce n'era solo uno.

Se si pensa in termini di separazione delle query dei comandi di Bertrand Meyer , è possibile pensare al modello come a due interfacce distinte, una che supporta i comandi e una che supporta le query.

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 {
    // ...
}

L'intuizione di Greg Young era che potevi separarlo in due oggetti separati

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

Dopo aver separato gli oggetti, ora hai la possibilità di separare le strutture dati che contengono lo stato dell'oggetto in memoria, in modo da poter ottimizzare per ogni caso; oppure memorizza / persiste lo stato di lettura separatamente dallo stato di scrittura.

Domanda n. 1: è effettivamente corretto usare il termine "modello di scrittura" nel modo in cui lo uso? Oppure "scrivi modello" significa sempre "negozio di eventi" in caso di ES? In tal caso, esiste un tipo di separazione tra i dati di cui ho bisogno per gestire i comandi e quelli di cui ho bisogno per gestire le query?

Il termine WriteModel è generalmente inteso come la rappresentazione mutabile del modello (ovvero: l'oggetto, non l'archivio di persistenza).

TL; DR: Penso che il tuo uso vada bene.

Ecco una domanda n. 2: è praticamente OK se uso l'elenco delle connessioni del modello di scrittura qui?

Questo è "bene" - ish. Concettualmente, non c'è niente di sbagliato nel modello di lettura e nel modello di scrittura che condividono le stesse strutture.

In pratica, poiché le scritture sul modello non sono in genere atomiche, esiste potenzialmente un problema quando un thread tenta di modificare lo stato del modello mentre un secondo thread tenta di leggerlo.

Ecco la domanda n. 3: quale modello dovrei usare per ottenere l'elenco delle connessioni? Dovrei usare il modello di scrittura? Dovrei usare il modello di lettura - GetFollowersQuery / GetFollowedUsersQuery? O dovrei fare in modo che il modello GetFriendFeedRecordsQuery stesso gestisca UserFollowedEvent e mantenga il proprio elenco di tutte le connessioni?

L'uso del modello write è la risposta sbagliata.

Scrivere più modelli di lettura, in cui ognuno è sintonizzato su un caso d'uso particolare, è del tutto ragionevole. Diciamo "il modello di lettura", ma è chiaro che potrebbero esserci molti modelli di lettura, ognuno dei quali è ottimizzato per un particolare caso d'uso e non implementa i casi in cui non ha senso.

Ad esempio, è possibile decidere di utilizzare un archivio valori-chiave per supportare alcune query e un database grafico per altre query o un database relazionale in cui tale modello di query ha senso. Cavalli per i corsi.

Nella tua specifica circostanza, dove stai ancora imparando il modello, il mio suggerimento sarebbe di mantenere il tuo design "semplice" - avere un modello di lettura che non condivida la struttura dei dati del modello di scrittura.


1

Ti incoraggio a considerare che hai un modello di dati concettuale.

Quindi il modello di scrittura è una materializzazione di quel modello di dati che è ottimizzato per gli aggiornamenti transazionali. A volte questo significa un database relazionale normalizzato.

Il modello di lettura è una materializzazione di quello stesso modello di dati ottimizzata per l'esecuzione delle query richieste dalle vostre applicazioni. Potrebbe essere comunque un database relazionale sebbene intenzionalmente denormalizzato per gestire le query con meno join.


(# 1) Per CQRS, il modello di scrittura non deve essere un archivio eventi.

(# 2) Non mi aspetterei che il modello di lettura memorizzi qualcosa che non è nel modello di scrittura, per uno perché in CQRS nessuno aggiorna il modello di lettura tranne il meccanismo di inoltro che mantiene il modello di lettura in sincronia con le modifiche al scrivi modello.

(# 3) In CQRS, le query dovrebbero andare contro il modello letto. Puoi fare diversamente: va bene, semplicemente non seguendo CQRS.


In sintesi, esiste un solo modello di dati concettuali. CQRS separa la rete di comando e le capacità dalla rete di query e dalle capacità. Dato che la separazione del modello di scrittura e del modello di lettura può essere ospitata utilizzando tecnologie molto diverse come ottimizzazione delle prestazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.