Parece haber un acuerdo generalizado en la comunidad OOP de que el constructor de la clase no debe dejar un objeto parcial o incluso sin inicializar.
¿Qué quiero decir con "inicialización"? En términos generales, el proceso atómico que lleva a un objeto recién creado a un estado en el que se encuentran todos sus invariantes de clase. Debe ser lo primero que le sucede a un objeto (solo debe ejecutarse una vez por objeto) y no se debe permitir que nada se apodere de un objeto no inicializado. (Por lo tanto, el consejo frecuente de realizar la inicialización de objetos directamente en el constructor de la clase. Por la misma razón, los
Initialize
métodos a menudo están mal vistos, ya que estos separan la atomicidad y hacen posible agarrar y usar un objeto que aún no está en un estado bien definido)
Problema: cuando CQRS se combina con el abastecimiento de eventos (CQRS + ES), donde todos los cambios de estado de un objeto se capturan en una serie ordenada de eventos (secuencia de eventos), me pregunto cuándo un objeto realmente alcanza un estado completamente inicializado: ¿Al final del constructor de la clase, o después de que el primer evento se haya aplicado al objeto?
Nota: me abstengo de usar el término "raíz agregada". Si lo prefiere, sustitúyalo cada vez que lea "objeto".
Ejemplo de discusión: Suponga que cada objeto está identificado de manera única por algún Id
valor opaco (piense en GUID) Un flujo de eventos que representa los cambios de estado de ese objeto se puede identificar en el almacén de eventos con el mismo Id
valor: (No nos preocupemos por el orden correcto de los eventos).
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Supongamos además que hay dos tipos de objetos Customer
y ShoppingCart
. Centrémonos en ShoppingCart
: Cuando se crean, los carritos de compras están vacíos y deben estar asociados con exactamente un cliente. Ese último bit es una clase invariante: un ShoppingCart
objeto que no está asociado a un Customer
está en un estado no válido.
En OOP tradicional, uno podría modelar esto en el constructor:
partial class ShoppingCart
{
public Id Id { get; private set; }
public Customer Customer { get; private set; }
public ShoppingCart(Id id, Customer customer)
{
this.Id = id;
this.Customer = customer;
}
}
Sin embargo, no sé cómo modelar esto en CQRS + ES sin terminar con la inicialización diferida. Dado que este simple bit de inicialización es efectivamente un cambio de estado, ¿no tendría que modelarse como un evento ?:
partial class CreatedEmptyShoppingCart
{
public ShoppingCartId { get; private set; }
public CustomerId { get; private set; }
}
// Note: `ShoppingCartId` is not actually required, since that Id must be
// known in advance in order to fetch the event stream from the event store.
Obviamente, este tendría que ser el primer evento en ShoppingCart
la secuencia de eventos de cualquier objeto, y ese objeto solo se inicializaría una vez que se le aplicara el evento.
Entonces, si la inicialización se convierte en parte de la "reproducción" de la secuencia de eventos (que es un proceso muy genérico que probablemente funcionaría igual, ya sea para un Customer
objeto o un ShoppingCart
objeto o cualquier otro tipo de objeto) ...
- ¿Debería el constructor no tener parámetros y no hacer nada, dejando todo el trabajo a algún
void Apply(CreatedEmptyShoppingCart)
método (que es muy parecido al mal vistoInitialize()
)? - ¿O debería el constructor recibir un flujo de eventos y reproducirlo (lo que hace que la inicialización sea atómica nuevamente, pero significa que el constructor de cada clase contiene la misma lógica genérica de "reproducir y aplicar", es decir, duplicación de código no deseada)?
- ¿O debería haber un constructor tradicional de OOP (como se muestra arriba) que inicializa correctamente el objeto, y luego todos los eventos excepto el primero están
void Apply(…)
ligados a él?
No espero que la respuesta proporcione una implementación demo totalmente funcional; Ya estaría muy feliz si alguien pudiera explicar dónde está defectuoso mi razonamiento, o si la inicialización de objetos es realmente un "punto débil" en la mayoría de las implementaciones de CQRS + ES.
Initialize
método) habrían ocupado. Eso me lleva a la pregunta, ¿cómo podría ser una fábrica tuya?En mi opinión, creo que la respuesta se parece más a su sugerencia # 2 para los agregados existentes; para nuevos agregados más como # 3 (pero procesando el evento como sugirió).
Aquí hay un código, espero que sea de alguna ayuda.
fuente
Bueno, el primer evento debe ser que un cliente cree un carrito de compras , de modo que cuando se crea el carrito de compras ya tenga una identificación de cliente como parte del evento.
Si un estado se mantiene entre dos eventos diferentes, por definición, es un estado válido. Entonces, si dice que un carrito de compras válido está asociado a un cliente, esto significa que debe tener la información del cliente al crear el carrito en sí
fuente
CreatedEmptyShoppingCart
evento en mi pregunta: tiene la información del cliente, tal como sugiere. Mi pregunta es más acerca de cómo dicho evento se relaciona o compite con el constructor de la claseShoppingCart
durante la implementación.Nota: si no desea utilizar la raíz agregada, "entidad" abarca la mayor parte de lo que está preguntando aquí mientras deja de lado las preocupaciones sobre los límites de las transacciones.
Aquí hay otra forma de pensar al respecto: una entidad es identidad + estado. A diferencia de un valor, una entidad es el mismo tipo incluso cuando su estado cambia.
Pero el estado mismo puede considerarse como un objeto de valor. Con esto quiero decir, el estado es inmutable; La historia de la entidad es una transición de un estado inmutable al siguiente: cada transición corresponde a un evento en la secuencia de eventos.
El método onEvent () es una consulta, por supuesto: currentState no cambia en absoluto, en su lugar, currentState está calculando los argumentos utilizados para crear el nextState.
Siguiendo este modelo, se puede considerar que todas las instancias de Carrito de compras parten del mismo valor inicial ...
Separación de inquietudes: ShoppingCart procesa un comando para determinar qué evento debe ser el próximo; el ShoppingCart State sabe cómo llegar al siguiente estado.
fuente