Según tengo entendido, la gran idea detrás de CQRS es tener 2 modelos de datos diferentes para manejar comandos y consultas. Estos se denominan "modelo de escritura" y "modelo de lectura".
Consideremos un ejemplo de clon de aplicaciones de Twitter. Aquí están los comandos:
- Los usuarios pueden registrarse ellos mismos.
CreateUserCommand(string username)
emiteUserCreatedEvent
- Los usuarios pueden seguir a otros usuarios.
FollowUserCommand(int userAId, int userBId)
emiteUserFollowedEvent
- Los usuarios pueden crear publicaciones.
CreatePostCommand(int userId, string text)
emitePostCreatedEvent
Si bien utilizo el término "evento" anterior, no me refiero a los eventos de 'abastecimiento de eventos'. Solo me refiero a señales que desencadenan actualizaciones de lectura de modelos. No tengo una tienda de eventos y hasta ahora quiero concentrarme en CQRS.
Y aquí están las consultas:
- Un usuario necesita ver la lista de sus publicaciones.
GetPostsQuery(int userId)
- Un usuario necesita ver la lista de sus seguidores.
GetFollowersQuery(int userId)
- Un usuario necesita ver la lista de usuarios que sigue.
GetFollowedUsersQuery(int userId)
- Un usuario necesita ver el "feed de amigos": un registro de todas las actividades de sus amigos ("su amigo John acaba de crear una nueva publicación").
GetFriedFeedRecordsQuery(int userId)
Para manejarlo CreateUserCommand
necesito saber si dicho usuario ya existe. Entonces, en este punto, sé que mi modelo de escritura debería tener una lista de todos los usuarios.
Para manejarlo FollowUserCommand
, necesito saber si el usuario A ya sigue al usuario B o no. En este punto, quiero que mi modelo de escritura tenga una lista de todas las conexiones entre usuarios y usuarios.
Y finalmente, para manejarlo CreatePostCommand
, no creo que necesite nada más, porque no tengo comandos como UpdatePostCommand
. Si tuviera esos, tendría que asegurarme de que exista una publicación, por lo que necesitaría una lista de todas las publicaciones. Pero debido a que no tengo este requisito, no necesito rastrear todas las publicaciones.
Pregunta # 1 : ¿es realmente correcto usar el término "modelo de escritura" de la forma en que lo uso? ¿O "escribir modelo" siempre significa "tienda de eventos" en el caso de ES? Si es así, ¿hay algún tipo de separación entre los datos que necesito para manejar comandos y los datos que necesito para manejar consultas?
Para manejarlo GetPostsQuery
, necesitaría una lista de todas las publicaciones. Esto significa que mi modelo de lectura debería tener una lista de todas las publicaciones. Voy a mantener este modelo escuchando PostCreatedEvent
.
Para manejar ambos GetFollowersQuery
y GetFollowedUsersQuery
, necesitaría una lista de todas las conexiones entre usuarios. Para mantener este modelo que voy a escuchar UserFollowedEvent
. Aquí hay una pregunta # 2 : ¿está prácticamente bien si uso la lista de conexiones del modelo de escritura aquí? ¿O debería crear un modelo de lectura por separado, porque en el futuro podría necesitarlo para tener más detalles que el modelo de escritura?
Finalmente, para manejarlo GetFriendFeedRecordsQuery
necesitaría:
- Escucha a
UserFollowedEvent
- Escucha a
PostCreatedEvent
- Sepa qué usuarios siguen a qué otros usuarios
Si el usuario A sigue al usuario B y el usuario B comienza a seguir al usuario C, deberían aparecer los siguientes registros:
- Para el usuario A: "Tu amigo, el usuario B, acaba de comenzar a seguir al usuario C"
- Para el usuario B: "Acaba de comenzar a seguir al usuario C"
- Para el usuario C: "El usuario B ahora te sigue"
Aquí está la Pregunta # 3 : ¿qué modelo debo usar para obtener la lista de conexiones? ¿Debo usar el modelo de escritura? ¿Debo usar el modelo de lectura - GetFollowersQuery
/ GetFollowedUsersQuery
? ¿O debería hacer que el GetFriendFeedRecordsQuery
modelo mismo maneje UserFollowedEvent
y mantenga su propia lista de todas las conexiones?