2010-12-04 18 views
7

todavía estoy luchando con lo que debe ser cuestiones básicas (y resuelto) relacionados con CQRS arquitectura de estilo:ejecución limitaciones basadas en el conjunto de CQRS

¿Cómo implementamos las reglas de negocio que se basan en un conjunto de agregado Roots?

Tome, por ejemplo, una aplicación de reserva. Puede permitirle reservar entradas para un concierto, asientos para una película o una mesa en un restaurante. En todos los casos, solo va a haber un número limitado de de 'artículos' a la venta.

Imaginemos que el evento o lugar es muy popular. Cuando las ventas se abren para un nuevo evento o intervalo de tiempo, las reservas comienzan a llegar muy rápido, tal vez muchas por segundo.

En el lado de la consulta, podemos escalar de forma masiva, y las reservas se colocan en una cola para ser manejadas asincrónicamente por un componente autónomo. Al principio, cuando sacamos Comandos de reserva de la cola, los aceptaremos, pero en un momento dado tendremos que iniciar rechazando el resto.

¿Cómo sabemos cuándo llegamos al límite?

Para cada comando de reserva tendríamos que consultar algún tipo de tienda para averiguar si podemos acomodar la solicitud. Esto significa que necesitaremos saber cuántas reservas ya hemos recibido en ese momento.

Sin embargo, si el Almacén de dominio es un almacén de datos no relacional como p. Ej. Tabla de almacenamiento de Windows Azure, que no puede muy bien hacer una opción SELECT COUNT(*) FROM ...

Uno sería mantener un separada agregada Raíz que simplemente se hace un seguimiento de la cuenta corriente, así:

  • AR: reserva (que cuántos?)
  • AR:/Hora del evento ranura/Fecha (recuento agregada)

el segundo agregado de raíz sería una agregación desnormalizado de la primera, pero cuando el underlyi ng data store no es compatible con las transacciones, entonces es muy probable que estas se desincronicen en escenarios de gran volumen (que es lo que intentamos abordar en primer lugar).

Una posible solución es serializar manejo de los comandos de reserva para que solo se maneje uno a la vez, pero esto va en contra de nuestros objetivos de escalabilidad (y redundancia).

Estos escenarios me recuerdan a los escenarios estándar "sin stock", pero la diferencia es que no podemos poner la reserva en orden inverso. Una vez que se agota un evento, se agota, por lo que no puedo ver qué acción compensatoria sería.

¿Cómo manejamos tales escenarios?

Respuesta

3

Después de pensar en esto durante algún tiempo, finalmente me di cuenta de que el problema subyacente está menos relacionado con CQRS que con la naturaleza no traccional de de servicios REST dispares.

Realmente se reduce a este problema: si necesita actualizar varios recursos, ¿cómo garantiza la coherencia si falla la segunda operación de escritura?

Imaginemos que queremos escribir actualizaciones en el Recurso A y el Recurso B en secuencia.

  1. de Recursos A se ha actualizado correctamente
  2. El intento de actualizar el recurso B falla

La primera operación de escritura no puede fácilmente ser revertido en la cara de una excepción, por lo que lo podemos hacer ? Capturar y suprimir la excepción para realizar una acción de compensación contra el Recurso A no es una opción viable. En primer lugar, es complejo de implementar, pero en segundo lugar no es seguro: ¿qué sucede si la primera excepción se produjo debido a una conexión de red fallida? En ese escenario, tampoco podemos escribir una acción de compensación contra el Recurso A.

La clave está en idempotency explícita.Si bien Windows Azure Queues no garantiza exactamente una vez semántica, sí garantiza al menos una vez semántica. Esto significa que, en caso de excepciones intermitentes, el mensaje será reproducido.

En el escenario anterior, esto es lo que sucede a continuación:

  1. de Recursos A se trató actualizada. Sin embargo, la reproducción se detecta para que el estado de A no se vea afectado. Sin embargo, la operación 'escribir' tiene éxito.
  2. El recurso B se ha actualizado con éxito.

Cuando todas las operaciones de escritura son idempotente, consistencia eventual se puede lograr con las repeticiones mensaje.

+0

Nada le impide agregar un tercer recurso, que contiene A y B como sub-recursos, y actualizar eso. Otra solución alternativa para crear un recurso de transacción temporal. No creo que mover las transacciones a los clientes de REST merezca la pena. – inf3rno

2

Pregunta interesante y con esta le está subiendo uno de los puntos débiles en CQRS.

La forma en que Amazon está manejando esto es teniendo el escenario comercial frente a un estado de error si los artículos solicitados están agotados. El estado de error es simplemente notificar al cliente por correo electrónico que los artículos solicitados no están actualmente en stock y el día estimado para el envío.

Sin embargo - esto no responde plenamente a su pregunta.

Pensando en un escenario de venta de boletos me aseguraría de decirle al cliente que la solicitud que dieron fue una solicitud de reserva . Que la solicitud de reserva sea procesada lo antes posible y que revivirán la respuesta final en un correo posterior. Al permitir esto, algunos clientes pueden recibir un correo electrónico con un rechazo a su solicitud.

Ahora. ¿Podríamos hacer esta rejesción menos dolorosa? Ciertamente. Mediante la inserción de una llave en nuestra caché distribuida con el porcentaje o la cantidad de artículos en stock y decrementar el contador cuando cada vez que se vende un artículo. De esta manera podríamos advertir al usuario antes de administrar la solicitud de reserva, digamos que si sólo el 10% del número inicial de las partidas se deja, que el cliente puede no ser capaz de conseguir el artículo en cuestión. Si el contador está en cero, simplemente nos negaremos a aceptar más solicitudes de reserva.

Mi punto es:

1) que el usuario sepa que se trata de una petición que están haciendo y que esto podría obtener negado 2) informar al usuario de que las posibilidades de éxito para conseguir el artículo en cuestión es bajo

No es exactamente una respuesta precisa a su pregunta, pero esto es cómo iba a manejar un escenario como este cuando se trata de CQRS.

+0

Estoy de acuerdo en que establecer expectativas también es una parte importante, pero lo tomé como algo dado, eso es (técnicamente) fácil de tratar. La pregunta es: ¿sin bloqueo y transacciones, cómo podemos disminuir de manera confiable y consistente el contador de asientos/boletos disponibles restantes en situaciones masivamente concurrentes? –

+0

el mostrador solo está allí para informar a los clientes y no como la respuesta final a su solicitud. Siempre necesita verificar con la base de datos para confirmarlo. Si tiene varios trabajadores de fondo que procesan las solicitudes de reserva, el contador necesita un bloqueo (memcached es atómico cuando llama al incremento (clave) o al decremento (clave), por lo que no hay problemas aquí). Pero dado su escenario de un recurso espacial (como entradas para un concierto), los pocos milisegundos para el bloqueo en el contador serían aceptables (recuerde que esto se reduciría a cero rápidamente y se rechazaría la nueva solicitud) –

+0

Si tomo esa ruta (y podría), la pregunta se reduce a: ¿cómo * haces * que cierras Cerradura en Tablas Azure? No creo que puedas ... –

1

El etag que la concurrencia optimista que se puede utilizar en lugar del bloqueo transaccional para actualizar un documento y manejar con seguridad posibles condiciones de carrera. Consulte los comentarios aquí http://msdn.microsoft.com/en-us/library/dd179427.aspx para obtener más información.

La historia podría ser algo como esto: El usuario A crea un evento E con tickets máximos de 2, eTag es 123. Debido a la gran demanda, 3 usuarios intentan comprar los boletos casi al mismo tiempo. usuario B crea una solicitud de reserva B. usuario C crea una solicitud de reserva C. usuario D crea una solicitud de reserva D.

Sistema S recibe solicitud de reserva B, lee evento con ETAG 123 y cambia el evento para tener 1 Boleto restante, S envía la actualización incluyendo eTag 123 que coincide con eTag original para que la actualización tenga éxito. El eTag ahora es 456. Se aprueba la solicitud de reserva y se notifica al usuario que tuvo éxito.

Otro sistema S2 recibe la solicitud de reserva C al mismo tiempo que el Sistema S procesaba la solicitud B, por lo que también lee el evento con eTag 123 y lo cambia a 1 ticket restante e intenta actualizar el documento. Esta vez, sin embargo, el eTag 123 no coincide, por lo que la actualización falla con una excepción. El sistema S2 intenta volver a intentar la operación volviendo a leer el documento que ahora tiene eTag 456 y un recuento de 1, por lo que lo reduce a 0 y vuelve a enviarse con eTag 456.

Desafortunadamente para el usuario C del usuario, el Sistema S comenzó a procesar al usuario D solicitó inmediatamente después del usuario B y también leyó el documento con eTag 456, pero dado que el sistema S es más rápido que el sistema S2, pudo actualizar el evento con eTag 456 antes del sistema S2, por lo que el usuario D también reservó con éxito su ticket. etag es ahora 789

Así Sistema S2 vuelve a fallar, sin embargo, le da una oportunidad más, pero esta vez cuando se lee el evento con ETAG 789 se ve que no hay entradas disponibles y por lo tanto niega solicitud de reserva del usuario C.

La manera de notificar a los usuarios que sus solicitudes de reserva fueron exitosas o no depende de usted.Puede sondear el servidor cada pocos segundos y esperar a que se actualice el estado de la reserva. mirada

+0

Gracias por la respuesta. Esto al menos me da una idea parcial de cómo se podría lograr esto. Según entiendo esto, se puede asegurar que una sola ACTUALIZACIÓN sea consistente, pero aún no podemos abarcar varias ACTUALIZACIONES en ningún tipo de 'transacción'. En otras palabras, no hay forma de garantizar que dos Raíces Agregadas separadas permanezcan sincronizadas, ya que la primera ACTUALIZACIÓN puede tener éxito mientras que la segunda falla. –

+0

La solución a * ese * problema, entonces, podría ser prescindir de uno de los AR y simplemente ir por un solo AR que encapsula toda la información sobre un solo evento/intervalo de tiempo/fecha, incluidos los detalles de la reserva. Esto podría funcionar muy bien para pequeños eventos, pero podría crear algunos documentos realmente grandes para un gran concierto con 100.000 personas en la audiencia. Eso podría alcanzar rápidamente el límite de tamaño de 1 MB de las filas de Azure Table. –

+0

Bueno, podría tener un agregado que haga referencia a una versión específica de otro agregado. Por lo tanto, si A hace referencia a B y desea actualizar transaccionalmente ambas, puede copiar B a C que tiene el cambio deseado, entonces actualice A para apuntar a C en lugar de B. Si la actualización a A falla, borre C y vuelva a intentarlo. –

1

Vamos en la perspectiva de negocio (que se ocupan en cosas similares - citas de reserva de slots libres) ...

La primera cosa en su análisis que me parece estar fuera es que no hay noción de que puede reservarse boleto/asiento/mesa. Estos son los recursos que se están reservando.

En caso de ser transaccional, puede utilizar alguna forma de exclusividad para garantizar que no se realice una doble reserva para el mismo boleto/asiento/mesa (más información en http://seabites.wordpress.com/2010/11/11/consistent-indexes-constraints). Este escenario requiere procesamiento de comandos sincrónico (pero aún concurrente).

En caso de no ser transaccional, puede monitorear retroactivamente la secuencia de eventos y compensar el comando. Incluso puede dar al usuario final la experiencia de esperar la confirmación de la reserva hasta que el sistema sepa con certeza, es decir, después del análisis del flujo de eventos, que el comando se completó y se compensó o no (lo que se reduce a "¿se realizó la reserva? ¿si o no?"). En otras palabras, la compensación podría formar parte del ciclo de confirmación.

Volvamos atrás un poco más ...

Cuando facturación está involucrado también (por ejemplo, la venta de boletos en línea), creo que todo este escenario se convierte en una saga de todos modos (boleto de reserva + entrada factura). Incluso sin facturar, tendrías una saga (reservar mesa + confirmar reserva) para que la experiencia sea creíble. Así que, aunque solo está enfocando un solo aspecto de reservar un boleto/mesa/asiento (es decir, si todavía está disponible), la transacción de "larga duración" no está completa hasta que lo pagué o hasta que lo confirme . La compensación ocurrirá de todos modos, liberando el boleto nuevamente cuando aborto la transacción por cualquier razón. La parte interesante ahora es cómo la empresa quiere lidiar con esto: tal vez algún otro cliente habría completado la transacción si le hubiéramos dado el mismo boleto. En este caso, la devolución puede ser más interesante cuando se reserva por duplicado un ticket/asiento/mesa, incluso ofreciendo un descuento en un evento próximo/similar para compensar las molestias. La respuesta radica en el modelo de negocio, no en el modelo técnico.

+0

+1 Buena respuesta. En última instancia, no era la respuesta que estaba buscando (ver mi propia respuesta para una explicación más detallada), pero sus observaciones son razonables. –