CQRS sin DDD y sin (o con?) ES: ¿qué es el modelo de escritura y qué es el modelo de lectura?

11

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 CreateUserCommandnecesito 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 GetFollowersQueryy 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 GetFriendFeedRecordsQuerynecesitarí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 GetFriendFeedRecordsQuerymodelo mismo maneje UserFollowedEventy mantenga su propia lista de todas las conexiones?

Andrey Agibalov
fuente
Tenga en cuenta que en los sistemas CQRS, el modelo de datos de consulta y el modelo de datos de comando pueden estar consumiendo datos de diferentes bases de datos. Y ambos modelos pueden estar viviendo independientemente el uno del otro (diferentes aplicaciones). Dicho esto, la respuesta es "depende" (como de costumbre). Puede interesar
Laiv

Respuestas:

7

Greg Young (2010)

CQRS es simplemente la creación de dos objetos donde anteriormente solo había uno.

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.

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

La idea de Greg Young fue que podías separar esto en dos objetos separados

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

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.

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?

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.

Aquí hay una pregunta # 2: ¿está prácticamente bien si uso la lista de conexiones del modelo de escritura aquí?

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.

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 debo hacer que el modelo GetFriendFeedRecordsQuery maneje el UserFollowedEvent y mantenga su propia lista de todas las conexiones?

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.

VoiceOfUnreason
fuente
1

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.

Erik Eidt
fuente