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?
fuente
Respuestas:
Greg Young (2010)
Si piensa en términos de la separación de consultas de comandos de Bertrand Meyer , puede pensar que el modelo tiene dos interfaces distintas, una que admite comandos y otra que admite consultas.
La idea de Greg Young fue que podías separar esto en dos objetos separados
Después de separar los objetos, ahora tiene la opción de separar las estructuras de datos que contienen el estado del objeto en la memoria, para que pueda optimizar para cada caso; o almacenar / persistir el estado de lectura por separado del estado de escritura.
El término WriteModel generalmente se entiende como la representación mutable del modelo (es decir, el objeto, no el almacén de persistencia).
TL; DR: Creo que su uso está bien.
Eso está "bien" - ish. Conceptualmente, no hay nada de malo en el modelo de lectura y el modelo de escritura que comparten las mismas estructuras.
En la práctica, debido a que las escrituras en el modelo no suelen ser atómicas, existe un problema potencial cuando un subproceso intenta modificar el estado del modelo mientras un segundo subproceso intenta leerlo.
Usar el modelo de escritura es la respuesta incorrecta.
Escribir múltiples modelos de lectura, donde cada uno está ajustado a un caso de uso particular, es totalmente razonable. Decimos "el modelo de lectura", pero se entiende que podría haber muchos modelos de lectura, cada uno de los cuales está optimizado para un caso de uso particular, y no implementa los casos en los que no tiene sentido.
Por ejemplo, puede decidir usar un almacén de valores clave para admitir algunas consultas, y una base de datos gráfica para otras consultas, o una base de datos relacional donde ese modelo de consulta tenga sentido. Caballos de carreras.
En su circunstancia específica, donde todavía está aprendiendo el patrón, mi sugerencia sería mantener su diseño "simple": tener un modelo de lectura que no comparta la estructura de datos del modelo de escritura.
fuente
Le animo a que considere que tiene un modelo de datos conceptual.
Entonces el modelo de escritura es una materialización de ese modelo de datos que está optimizado para actualizaciones transaccionales. A veces esto significa una base de datos relacional normalizada.
El modelo de lectura es una materialización de ese mismo modelo de datos optimizado para realizar las consultas que su (s) aplicación (es) necesita (s). Todavía podría ser una base de datos relacional aunque intencionalmente desnormalizada para manejar consultas con menos uniones.
(# 1) Para CQRS, el modelo de escritura no tiene que ser una tienda de eventos.
(# 2) No esperaría que el modelo de lectura almacene nada que no esté en el modelo de escritura, por ejemplo, porque en CQRS, nadie actualiza el modelo de lectura excepto el mecanismo de reenvío que mantiene el modelo de lectura sincronizado con los cambios en el Escribir modelo.
(# 3) En CQRS, las consultas deben ir en contra del modelo de lectura. Puedes hacer diferentes: está bien, simplemente no seguir CQRS.
En resumen, solo hay un modelo de datos conceptual. CQRS separa la red de comandos y las capacidades de la red de consulta y las capacidades. Dada esa separación, el modelo de escritura y el modelo de lectura se pueden alojar utilizando tecnologías muy diferentes como una optimización del rendimiento.
fuente