Following domain-driven design, I'm trying to implement an outbox pattern, that will save domain events on an AggregateRoot derived entity in the same "transaction" in CosmosDb.
I'm aware I could do this using batching in the CosmosDb SDK, however there is no way to do that currently with Entity Framework, and looks like it's not coming in version 10 either: https://github.com/dotnet/efcore/issues/17308
AggregateRoot base class:
public abstract class AggregateRoot<TAggregate> : IDomainEventAccumulator where TAggregate : AggregateRoot<TAggregate>
{
public Guid Id { get; protected set; } = Guid.NewGuid();
public ICollection<IDomainEvent> DomainEvents { get; } = new List<IDomainEvent>();
protected void AddDomainEvent(IDomainEvent domainEvent)
{
DomainEvents.Add(domainEvent);
}
...
}
Derived class:
public class Partner : AggregateRoot<Partner>
{
public Partner(string name)
{
Name = name;
AddDomainEvent(new PartnerCreatedEvent(Id));
}
}
As you can see, IDomainEvent may have multiple implementations, and needs to be serialized/de-serialized to the correct types. I have had this working on the single entity by doing the following in the EntityTypeConfiguration:
public class PartnerConfiguration : IEntityTypeConfiguration<Partner>
{
public void Configure(EntityTypeBuilder<Partner> builder)
{
builder.ToContainer(nameof(CosmosDbContext.Partners));
builder.HasPartitionKey(d => d.Id);
var assembly = Assembly.Load("MyApplication.Domain");
var domainEventTypes = assembly.GetTypes().Where(t => typeof(IDomainEvent)
.IsAssignableFrom(t) && !t.IsAbstract)
.ToArray();
var serializerOptions = new JsonSerializerOptions()
{
TypeInfoResolver = new EventTypeResolver(domainEventTypes)
};
builder.Property(c => c.DomainEvents).HasConversion(
v => JsonSerializer.Serialize(v, serializerOptions),
v => JsonSerializer.Deserialize<List<IDomainEvent>>(v, serializerOptions));
}
}
Ideally I would like to blanket apply this to all classes derived from AggregateRoot base class, but cannot find a way of doing this. I have also tried using the following on the DbContext, but cannot find a way to pass the TypeInfoResolver in for JsonSerializer:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<IDomainEvent>(c => c.HaveConversion<DomainEventConversion>());
}
Has anyone got an idea how I can achieve the goal I can guarantee saving these events at the same time as calling SaveChanges() on the DbContext? This does not have to include the use of JsonSerializer, but this is as close as I've gotten so far.