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

11

Per quanto ho capito, l'idea alla base di CQRS consiste nel disporre di 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 di clone dell'applicazione Twitter. Ecco i comandi:

  • Gli utenti possono registrarsi. CreateUserCommand(string username) emette UserCreatedEvent
  • Gli utenti possono seguire altri utenti. FollowUserCommand(int userAId, int userBId) emette UserFollowedEvent
  • Gli utenti possono creare post. CreatePostCommand(int userId, string text) emette PostCreatedEvent

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

E qui ci sono le domande:

  • Un utente deve vedere l'elenco dei suoi post. %codice%
  • Un utente deve vedere l'elenco dei suoi follower. %codice%
  • Un utente deve vedere l'elenco degli utenti che segue. %codice%
  • Un utente deve vedere il "feed degli amici", un registro delle attività di tutti i suoi amici ("il tuo amico John ha appena creato un nuovo post"). %codice%

Per gestire GetPostsQuery(int userId) Ho 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 GetFollowersQuery(int userId) Devo sapere se l'utenteA segue già l'utente B o no. A questo punto voglio che il mio modello di scrittura abbia un elenco di tutte le connessioni utente-utente-seguito.

E infine, per gestire GetFollowedUsersQuery(int userId) non penso di aver bisogno di nient'altro, perché non ho comandi come GetFriedFeedRecordsQuery(int userId) . Se ne avessi bisogno, dovrei assicurarmi che il post esista, 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 utilizzare il termine "modello di scrittura" per come lo uso? Oppure "scrivere modello" significa sempre "negozio di eventi" in caso di ES? In tal caso, esiste un qualche tipo di separazione tra i dati di cui ho bisogno per gestire i comandi e i dati che devo gestire per le query?

Per gestire CreateUserCommand , 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. Ho intenzione di mantenere questo modello ascoltando FollowUserCommand .

Per gestire sia CreatePostCommand che UpdatePostCommand , avrei bisogno di un elenco di tutte le connessioni tra gli utenti. Per mantenere questo modello, ascolterò GetPostsQuery . Ecco una domanda n. 2 : è praticamente OK se utilizzo qui l'elenco delle connessioni del modello di scrittura? O dovrei creare un modello di lettura separato, perché in futuro potrei aver bisogno di avere più dettagli di quanti ne abbia il modello di scrittura?

Infine, per gestire PostCreatedEvent avrei bisogno di:

  • Ascolta GetFollowersQuery
  • Ascolta GetFollowedUsersQuery
  • 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, dovrebbero apparire i seguenti record:

  • Per l'utente A: "L'utente amico 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 ti sta seguendo ora"

Ecco la domanda n. 3 : quale modello dovrei utilizzare per ottenere l'elenco delle connessioni? Dovrei usare il modello di scrittura? Dovrei usare read model - UserFollowedEvent / GetFriendFeedRecordsQuery ? O dovrei rendere UserFollowedEvent il modello stesso a gestire PostCreatedEvent e mantenere il proprio elenco di tutte le connessioni?

    
posta Andrey Agibalov 25.11.2016 - 19:19
fonte

2 risposte

7

Greg Young (2010)

CQRS is simply the creation of two objects where there was previously only one.

Se pensi in termini di Separazione query comandi di Bertrand Meyer, puoi pensare che il modello abbia due interfacce distinte , uno che supporta i comandi e uno 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 puoi separare questo 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 di dati che contengono lo stato dell'oggetto in memoria, in modo da poter ottimizzare per ogni caso; o memorizzare / persistere lo stato di lettura separatamente dallo stato di scrittura.

Question #1: is it actually correct to use the term "write model" they way I use it? Or does "write model" always stand for "event store" in case of ES? If so, is there any kind of separation between the data I need to handle commands and the data I need to handle queries?

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

TL; DR: Penso che il tuo utilizzo sia corretto.

Here is a Question #2: is it practically OK if I use write model's list of connections here?

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

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

Here's the Question #3: what model should I use to get the list of connections? Should I use write model? Should I use read model - GetFollowersQuery/GetFollowedUsersQuery? Or should I make GetFriendFeedRecordsQuery model itself handle the UserFollowedEvent and maintain its own list of all connections?

L'uso del modello di scrittura è la risposta sbagliata.

Scrivere più modelli di lettura, in cui ognuno è sintonizzato su un particolare caso d'uso è assolutamente ragionevole. Diciamo "il modello letto", 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 lo fa 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 abbia senso. Cavalli per i corsi.

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

    
risposta data 25.11.2016 - 23:45
fonte
1

Ti incoraggio a considerare di avere 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 dello stesso modello di dati ottimizzato per l'esecuzione delle query richieste dalle applicazioni. Potrebbe comunque essere un database relazionale benché denormalizzato per gestire le query con meno join.

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

(# 2) Non mi aspetto che il modello di lettura memorizzi tutto ciò che non è nel modello di scrittura, perché in CQRS, nessuno aggiorna il modello letto eccetto il meccanismo di inoltro che mantiene il modello letto in sincronia con modifiche al modello di scrittura.

(# 3) In CQRS, le query dovrebbero andare contro il modello di lettura. Puoi fare qualcosa di diverso: va bene, semplicemente non seguendo CQRS.

In sintesi, esiste un solo modello di dati concettuali. CQRS separa la rete e le capacità di comando dalla rete di query e dalle funzionalità. Data questa separazione, il modello di scrittura e il modello di lettura possono essere ospitati utilizzando tecnologie molto diverse come ottimizzazione delle prestazioni.

    
risposta data 25.11.2016 - 23:04
fonte

Leggi altre domande sui tag