Cómo manejar los errores posteriores a la validación en el comando (DDD + CQRS)

18

Por ejemplo, cuando envía un formulario de Registro, debe verificar Domain Model( WriteModelin CQRS) que está en un estado válido (por ejemplo, sintaxis de dirección de correo electrónico, edad, etc.).

Luego crea un Commandy lo envía a un Command Bus.

Entiendo que los comandos no deben devolver nada.

Entonces, ¿cómo manejas un error más allá Command Bus? (Por ejemplo, un usuario se registró 1 segundo antes con el mismo username/email).

¿Cómo sabes que ese comando falló y cómo sabes el error?

JorgeeFG
fuente
2
No necesitas un bus de eventos. ¿Por qué todos piensan que necesita un bus de eventos al implementar CQRS? Simplemente no lo sé. CQRS simplemente significa que separa por completo las escrituras y las lecturas, hace las preocupaciones por separado. Tienes CQRS siempre que lo hagas. Sus comandos ni siquiera necesitan ser asíncronos. Si los comandos síncronos con informe de errores funcionan para usted, hágalo.
Andy
1
Los comandos no deben devolver nada cuando tienen éxito . La idea comenzó con CQS , que tenía la implicación de que el comando aún podía lanzar una excepción por error. Lo que estás describiendo es un comando unidireccional. Ver esta respuesta en relación con eso.
Kasey Speakman

Respuestas:

4

Entiendo que los comandos no deben devolver nada.

Esa es una vista, pero no está completamente en piedra. Considere las escrituras (PUT, POST, DELETE) en HTTP: todos estos mensajes son comandos, en el sentido de que son mensajes con solicitud de que el recurso cambie de estado y, sin embargo, todos devuelven respuestas.

Entonces, ¿cómo manejas un error más allá de Command Bus? (Por ejemplo, un usuario se registró 1 segundo antes con el mismo nombre de usuario / correo electrónico).

¿Cómo sabes que ese comando falló y cómo sabes el error?

Entonces, en un caso en el que se está comunicando directamente con el controlador de comandos, un mensaje devuelto es una forma perfectamente razonable de reconocer que el comando ha sido recibido y procesado.

Si está utilizando un software intermedio, como un bus, que le impide comunicarse directamente con el objetivo, le sugiero que busque patrones de mensajes asíncronos: ¿cómo hace que el controlador de comandos envíe un mensaje al ¿llamador?

Una idea es suscribirse al resultado del comando; Esto se basa en algunas de las ideas de los Patrones de integración empresarial de Hohpe. La idea básica es que, dado que el cliente está familiarizado con el mensaje de comando que se envió, está bien posicionado para suscribirse a cualquier mensaje nuevo publicado como consecuencia del mensaje de comando. El controlador de comandos, después de guardar los datos en el libro de registro, publica eventos anunciando que el cambio fue exitoso, y el cliente se suscribe a esos eventos, reconociendo los eventos correctos al considerar la coincidencia de varios identificadores en el mensaje (identificación de causalidad, ID de correlación , y así sucesivamente).

Los enfoques alternativos son un poco más directos. Una sería incluir en el mensaje una devolución de llamada, que puede ser invocada por el controlador de comandos después de que el mensaje se maneje con éxito.

Una alternativa muy similar es reservar espacio en el mensaje de comando para que el manejador de comandos escriba el acuse de recibo, ya que el cliente ya tiene el mensaje de comando en cuestión, el circuito ya está completo. Piense " promesa " o " futuro completable". El mensaje le dice al controlador del comando dónde escribir el acuse de recibo; Al hacerlo, se le indica al cliente (cierre de cuenta regresiva) que el reconocimiento está disponible.

Y, por supuesto, tiene la opción adicional de eliminar el middleware que parece estar impidiendo simplemente hacer lo correcto.

Por ejemplo, un usuario registrado 1 segundo antes con el mismo nombre de usuario / correo electrónico

Si está manejando el registro de usuario de manera idempotente, eso no sería necesariamente un error: repetir mensajes hasta que se observe una respuesta es una forma común de garantizar al menos una entrega.

VoiceOfUnreason
fuente
2

Por ejemplo, cuando envía un formulario de Registro, debe verificar en el Modelo de dominio (WriteModel en CQRS) que está en un estado válido (ejemplo, sintaxis de dirección de correo electrónico, antigüedad, etc.)

Hay muchos tipos de validación. La validación cuando verifica la sintaxis de la dirección de correo electrónico y el formato de antigüedad es un tipo de validación que un Comando puede hacer. Esto no es realmente una preocupación de dominio. Puede parecer así porque algunos expertos en dominios le dirían esas especificaciones, pero debe hacer este tipo de validación en la creación del Comando. De hecho, la idea general es hacer la validación lo antes posible porque después de crear un comando y enviarlo a un BUS, es más complicado tomar medidas.

Entonces, ¿cómo manejas un error más allá de Command Bus? (Por ejemplo, un usuario se registró 1 segundo antes con el mismo nombre de usuario / correo electrónico).

Este tipo de validación se discute mucho en la comunidad CQRS, desde el comienzo de CQRS. Donde hacerlo Es muy debatido. Personalmente utilizo el siguiente enfoque: antes de enviar el comando al BUS, marco el nombre de usuario / correo electrónico como tomado de manera centralizada (es decir, una restricción de índice única en nombre de usuario / correo electrónico). Después de eso envié el comando. El inconveniente es que, si el comando falla, durante un corto período de tiempo, ese nombre de usuario se toma y no se usa; Esto es aceptable para mi negocio, probable para su negocio.

¿Cómo sabes que ese comando falló y cómo sabes el error?

Si el Agregado rechaza un comando asincrónico debido a un dominio invariable, entonces se deben tomar algunas medidas emitiendo un comando compensatorio que de alguna manera notifique al emisor del comando (es decir, envíe un correo electrónico de explicación de que el comando falló).

El problema con el correo electrónico duplicado es que no puede enviar un correo electrónico porque esa dirección de correo electrónico pertenece a otra persona, por eso lo reviso antes de enviar el comando al bus.

Constantin Galbenu
fuente
1

La validación debe realizarse en un decorador. Entonces, cualquier comando que necesite validación podría decorarse como tal.

Las validaciones pueden manejarse con excepciones si su regreso es nulo con su comando para que puedan ser recogidas con llamadas de sincronización o asíncronas con el resultado de la tarea devuelta.

Otra posibilidad es pensar en la validación como un tipo de "consulta" que devolvería un resultado de validación. Ejecute la consulta de validación y, si se pasa, ejecute el comando. Esta sería una alternativa al enfoque del decorador.

Jon Raynor
fuente
Me gusta el enfoque de excepción porque es la forma más limpia. ¿Pero no son las excepciones demasiado pesadas para esto?
EresDev
1
HI @EresDev - Sí, son pesados. Pero no sé cuán "válidos" son sus datos. Digamos que de 1000 registros 2 no son válidos. Las excepciones podrían ser viables ya que son condiciones verdaderamente excepcionales. Ahora considere 1000 registros con 200 no válidos. En esa causa, sí, se deben evitar las excepciones porque el 20% de sus datos no son válidos. Por lo tanto, le dejo a usted decidir si elige o no este enfoque en función de su validez de datos actual. :)
Jon Raynor
Añadiré a esto diciendo que si mantenemos el ejemplo del formulario de registro, donde creo que el 50% o incluso más usuarios insertan primero datos no válidos, la excepción todavía está bien. En el backend de la aplicación web, la mayoría de las veces obtiene datos válidos porque las interfaces también están validando. Solo obtienes si algo ha perdido la validación de la interfaz por casualidad o si alguien está jugando. Entonces, las excepciones están muy bien en este caso.
EresDev