Parece haver um amplo consenso na comunidade OOP de que o construtor de classes não deve deixar um objeto parcial ou totalmente não inicializado.
O que quero dizer com "inicialização"? Grosso modo, o processo atômico que leva um objeto recém-criado a um estado em que todos os seus invariantes de classe se mantêm. Deve ser a primeira coisa que acontece com um objeto (ele deve ser executado apenas uma vez por objeto) e nada deve ter permissão para se apossar de um objeto não inicializado. (Portanto, o conselho frequente de executar a inicialização do objeto diretamente no construtor da classe. Pelo mesmo motivo, os
Initialize
métodos geralmente são desaprovados, pois quebram a atomicidade e tornam possível obter e usar um objeto que ainda não está em um estado bem definido.)
Problema: Quando o CQRS é combinado com a fonte de eventos (CQRS + ES), onde todas as alterações de estado de um objeto são capturadas em uma série ordenada de eventos (fluxo de eventos), fico me perguntando quando um objeto realmente atinge um estado totalmente inicializado: No final do construtor da classe, ou após o primeiro evento ter sido aplicado ao objeto?
Nota: Não uso o termo "raiz agregada". Se preferir, substitua-o sempre que ler "objeto".
Exemplo para discussão: Suponha que cada objeto seja identificado exclusivamente por algum Id
valor opaco (pense em GUID). Um fluxo de eventos representando as alterações de estado desse objeto pode ser identificado no armazenamento de eventos pelo mesmo Id
valor: (Não vamos nos preocupar com a ordem correta do evento.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Suponha ainda que existem dois tipos de objetos Customer
e ShoppingCart
. Vamos nos concentrar em ShoppingCart
: Quando criados, os carrinhos de compras estão vazios e devem estar associados a exatamente um cliente. Esse último bit é uma classe invariável: um ShoppingCart
objeto que não está associado a Customer
está em um estado inválido.
Na OOP tradicional, pode-se modelar isso no construtor:
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;
}
}
No entanto, não sei como modelar isso no CQRS + ES sem terminar com a inicialização adiada. Como esse pequeno pedaço de inicialização é efetivamente uma alteração de estado, não precisa ser modelado como um 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, esse teria que ser o primeiro evento no ShoppingCart
fluxo de eventos de qualquer objeto, e esse objeto só seria inicializado quando o evento fosse aplicado a ele.
Portanto, se a inicialização se tornar parte do fluxo de eventos "playback" (que é um processo muito genérico que provavelmente funcionaria da mesma forma, seja para um Customer
objeto ou ShoppingCart
objeto ou qualquer outro tipo de objeto) ...
- O construtor deve ter menos parâmetros e não fazer nada, deixando todo o trabalho para algum
void Apply(CreatedEmptyShoppingCart)
método (que é praticamente o mesmo que o desaprovadoInitialize()
)? - Ou o construtor deve receber um fluxo de eventos e reproduzi-lo (o que torna a inicialização atômica novamente, mas significa que o construtor de cada classe contém a mesma lógica genérica de "reproduzir e aplicar", ou seja, duplicação indesejada de código)?
- Ou deveria haver um construtor OOP tradicional (como mostrado acima) que inicializa corretamente o objeto e, em seguida, todos os eventos, exceto o primeiro, estão
void Apply(…)
ligados a ele?
Não espero que a resposta forneça uma implementação de demonstração totalmente funcional; Eu já ficaria muito feliz se alguém pudesse explicar onde meu raciocínio é defeituoso ou se a inicialização do objeto é realmente um "ponto problemático" na maioria das implementações do CQRS + ES.
Initialize
método) teriam ocupado. Isso me leva à pergunta: como seria sua fábrica?Na minha opinião, acho que a resposta é mais parecida com a sua sugestão nº 2 para agregados existentes; para novos agregados mais parecidos com o nº 3 (mas processando o evento conforme sugerido).
Aqui está algum código, espero que seja de alguma ajuda.
fonte
Bem, o primeiro evento deve ser que um cliente crie um carrinho de compras ; portanto, quando o carrinho de compras for criado, você já terá um ID de cliente como parte do evento.
Se um estado mantém entre dois eventos diferentes, por definição, é um estado válido. Portanto, se você diz que um carrinho de compras válido está associado a um cliente, isso significa que você precisa ter as informações do cliente ao criar o próprio carrinho
fonte
CreatedEmptyShoppingCart
evento na minha pergunta: ele contém as informações do cliente, como você sugere. Minha pergunta é mais sobre como esse evento se relaciona ou compete com o construtor de classesShoppingCart
durante a implementação.Nota: se você não quiser usar raiz agregada, "entidade" abrange a maior parte do que você está perguntando aqui, além de abordar as preocupações sobre os limites da transação.
Aqui está outra maneira de pensar sobre isso: uma entidade é identidade + estado. Ao contrário de um valor, uma entidade é a mesma pessoa, mesmo quando seu estado muda.
Mas o próprio estado pode ser pensado como um objeto de valor. Com isso quero dizer, o estado é imutável; o histórico da entidade é uma transição de um estado imutável para o próximo - cada transição corresponde a um evento no fluxo de eventos.
O método onEvent () é uma consulta, é claro - currentState não é alterado, em vez disso, currentState está calculando os argumentos usados para criar o nextState.
Seguindo esse modelo, todas as instâncias do carrinho de compras podem ser consideradas como iniciando no mesmo valor inicial ....
Separação de preocupações - o ShoppingCart processa um comando para descobrir qual evento deve ser o próximo; o estado do ShoppingCart sabe como chegar ao próximo estado.
fonte