Sto lavorando a un progetto per aiutare a imparare DDD e sto cercando di fare CQRS e Event Sourcing. Il codice è in C #.
Per questo esempio, diciamo che ho 2 aggregati, Customer
e Product
. Il mio repository aggregato ha un metodo get Get<TAggregate>(Guid id)
che carica tutti gli eventi per quell'ID, crea un'istanza TAggregate
vuota, quindi riproduce tutti gli eventi su quell'istanza. L'aggregato ignora gli eventi che non sa cosa fare con.
Il sotto funzionerà come previsto, ogni aggregato è ricostituito dai suoi eventi nell'archivio degli eventi
Customer customer = repo.Get<Customer>(customerId);
Product product = repo.Get<Product>(productId);
Tuttavia, se provo a ricostituire un aggregato da una raccolta di eventi da un aggregato diverso, al momento non viene generato alcun errore, ma l'istanza aggregata ignora qualsiasi evento che non sa cosa fare in modo da è lasciato in questo stato "pulito" come se gli fossero passati zero eventi.
Customer customer = repo.Get<Customer>(productId);
Product product = repo.Get<Product>(customerId);
Vedo due modi per risolvere questo problema: - L'aggregato stesso controlla per assicurarsi che sia in uno stato valido prima di consentire qualsiasi opperation del dominio. - I tipi di eventi sono esplicitamente associati a tipi di aggregati specifici, gli eventi passati al risultato di tipo aggregato sbagliato in un'eccezione.
L'aggregato garantisce che sia in uno stato valido
Esempio:
public class Product : AggregateRoot
{
private Guid _id;
private bool _isConstructed;
public Product(Guid id, ...)
{
// enforce domain rules here
ApplyChange(new ProductAddedEvent(id, ...));
}
public void UpdatePrice(decimal newPrice)
{
if(!_isConstructed)
throw new Exception(...);
// enforce domain rules here
ApplyChange(new ProductPriceUpdatedEvent(_id, newPrice));
}
private void Apply(ProductAddedEvent e)
{
_id = e.Id;
_isConstructed = true;
}
private void Apply(ProductPriceUpdatedEvent e)
{
...
}
}
Potrebbe funzionare, ma mi sembra che possa sfuggire di mano molto rapidamente, con conseguente ingombrante e scomodo codice.
I tipi di eventi sono esplicitamente associati a tipi di aggregati specifici
Impostazione simile all'esempio precedente.
public abstract class Event<TAggregate> where TAggregate : AggregateRoot
{
public bool IsValidFor(AggregateRoot aggregate)
{
return aggregate is TAggregate;
}
}
public class ProductAddedEvent : Event<Product>
{
...
}
public abstract class AggregateRoot
{
public void Reconstitute(Event[] events)
{
foreach(Event event in events)
{
if(!event.IsValidFor(this)
throw new Exception(...);
ApplyEvent(event);
}
}
}
Questo approccio ha più senso per me. C'è qualche potenziale odore qui che non vedo? C'è qualcos'altro che non sto considerando?
Modifica:
Un'altra idea che ho avuto è forse che il gestore di comandi ha bisogno di convalidare il comando, interrogando il modello letto per assicurarsi che esista un aggregato di tipo previsto con l'ID specificato. Ma anche se questo risulta essere l'approccio corretto, c'è qualcosa di sbagliato nell'associare eventi a tipi di aggregati specifici?