Reputation: 421
This may be more of a general NOSQL infrastructure question.
With an event sourcing system I'm saving Event documents to ravendb.
In my domain model there are multiple types of events, an basic example below:
public abstract class Event
{
public Guid AggregateId { get; set; }
public Event(Guid aggregateId)
{
AggregateId = aggregateId;
}
}
public class NewPersonEvent : Event
{
public string Name { get; set; }
public NewPersonEvent(Guid id, string name) : Base(id)
{
Name = name;
}
}
public class NewOrderEvent : Event
{
public DateTime OrderDate { get; set; }
public double OrderCost { get; set;}
public NewOrderEvent(Guid id, DateTime date, double cost) : Base(id)
{
OrderDate = date;
OrderCost = cost;
}
}
An event is persisted as an Event document no matter what type of aggregate the event is from.
Is this the best approach to use one Document type in ravendb for all events. Or is there any benefits of grouping the document types per aggregate
Instead of having just 'Event' documents, to instead have 'PersonEvent' documents and 'OrderEvent' documents.
What are the pros and cons of each of the two approaches, specifically is there any performance issues?
Upvotes: 3
Views: 161
Reputation: 365
Are you overriding the default tag name for events as...
docStore.Conventions.FindTypeTagName = type => typeof(Event).IsAssignableFrom(type) ? DocumentConvention.DefaultTypeTagName(typeof(Event)) : DocumentConvention.DefaultTypeTagName(type);
Then whenever you do a query on Event
it just works and retrieves all events.
session.Query<Event>()
If so, you can shoot yourself in the foot because if you just want a subset of the events and you do this session.Query<NewPersonEvent>()
it's going to retrieve all the events since you overrode the tag convention in the beginning.
You can still do it another way but it wouldn't be as straight forward (e.g. having an enum for the event type and filter by the enum).
I would vote to NOT override default RavenDB behavior, leave the different event types as their own doc collections, and simply use a multi-map index.
The Raven docs state that static indexes are preferred over dynamic indexes so it shouldn't be a performance concern. docs:
If you don't mess with the tag convention, but you could create more indexes for a sub-set of the events, create map/reduce indexes to aggregate counts per event type, and much more.
The index would be a multi-map index and you have 2 flavors you can choose from.
Option 1
public class Events_All : AbstractMultiMapIndexCreationTask
{
public Events_All()
{
AddMap<NewPersonEvent>(newPersonEvents =>
from newPersonEvent in newPersonEvents
select new
{
Id = newPersonEvent.AggregateId
});
AddMap<NewOrderEvent>(newOrderEvents =>
from newOrderEvent in newOrderEvents
select new
{
Id = newOrderEvent.AggregateId
});
}
}
Option 2 (Reflection)
public class Events_All2 : AbstractMultiMapIndexCreationTask
{
public Events_All2()
{
AddMapForAll<Event>(events =>
from @event in events
select new
{
Id = @event.AggregateId
});
}
}
Here's an example test using the RavenDB.Tests.Helpers
and Shouldly
NuGet packages. It's using the first example index but you can modify it to use the second as well.
public class MultimapIndexTest : RavenTestBase
{
private readonly Random _random = new Random();
[Fact]
public void GetAll_HasBoth_Success()
{
//Arrange
const string personName = "Jimmy";
double randomCost = _random.Next();
var event1 = new NewPersonEvent(Guid.NewGuid(), personName);
var event2 = new NewOrderEvent(Guid.NewGuid(), DateTime.Now, randomCost);
using (var docStore = NewDocumentStore())
{
docStore.ExecuteIndex(new Events_All());
using (var sesion = docStore.OpenSession())
{
sesion.Store(event1);
sesion.Store(event2);
sesion.SaveChanges();
}
docStore.WaitForStaleIndexesToComplete();
//Act
var events = GetAll(docStore).ToList();
//Assert
events.ShouldNotBeEmpty();
events.Count().ShouldBe(2);
var newPersonEvent = events.FirstOrDefault(x => x is NewPersonEvent) as NewPersonEvent;
newPersonEvent.ShouldNotBe(null);
newPersonEvent.Name.ShouldBe(personName);
var newOrderEvent = events.FirstOrDefault(x => x is NewOrderEvent) as NewOrderEvent;
newOrderEvent.ShouldNotBe(null);
newOrderEvent.OrderCost.ShouldBe(randomCost);
}
}
private IEnumerable<Event> GetAll(DocumentStore docStore)
{
using (var session = docStore.OpenSession())
{
return session.Query<Event, Events_All>();
}
}
}
Upvotes: 1