Memento pattern/AggregateBase.GetSnapshot

Coordinator
Nov 19, 2011 at 8:51 PM

I'm currently reviewing the code Jörg has checked in to get to understand the architecture and how everything's working together.

However, I'd like the advice of someone having some experience with CommonDomain for the following question: How is the snapshot stuff meant to be implemented? Suppose, the Child class implemented the GetSnapshot method, what should it return? First, it probably needs to store all of its data (firstName, lastName, birthday, related people) so that when creating the snapshot, we can copy the data. Second, the snapshot needs exactly the same data: firstName, lastName, birthday, related people. GetSnapshot would return a memento object with a copy of the data. Child needs a constructor to accept a snapshot.

Here's an idea of how to implement this without much code duplication:

public class AggregateMemento<T> : IMemento
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public T Data { get; set; }
}
public class Child : AggregateBase
{
  private class ChildData
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthday { get; set; }
    public List<Person> RelatedPeople { get; set; }
  
    public ChildData Clone()
    {
      return new ChildData 
      { 
        FirstName = FirstName, 
        LastName = LastName, 
        Birthday = Birthday, 
        RelatedPeople = new List<Person> (RelatedPeople) 
      };
    }
  }
  
  private ChildData data;
  
  private Child(Guid id, string firstname, string lastname, DateTime birthday)
  {
    RaiseEvent(new NewChildRegistered(id, firstname, lastname, birthday));
  }
  
  private Child (Guid id, IMemento snapshot)
  {
    if (id != snapshot.Id)
      throw new ArgumentException ("Snapshot must match ID.", "snapshot");
      
    var childData = (AggregateMemento<ChildData>) snapshot.Data;
    Id = id;
    Verison = snapshot.Version;
    data = childData.Clone();
  }
  
  private void Apply(NewChildRegistered @event)
  {
    Id = @event.ChildId;
    data = new ChildData 
    { 
      FirstName = @event.FirstName, 
      LastName = @event.LastName, 
      Birthday = @event.Birthday, 
      RelatedPeople = new List<Person>()
    };
  }
  
  protected override IMemento GetSnapshot()
  {
    return new AggregateMemento<ChildData>
    {
      Id = Id,
      Version = Version,
      Data = data.Clone();
    };
  }
}

Is this how it's supposed to be implemented? Or is there some other, better approach to this?

Regards,
Fabian

Coordinator
Nov 20, 2011 at 8:49 AM

The snapshot has to carry only the information you need to validate your business rules -> as long as first name, last name, birthday etc are not used in any validation logic, they don't have to be persisted in the snapshot, because they are still available in our read model for displaying in the UI.

Coordinator
Nov 20, 2011 at 5:21 PM

You're right, of course.

How would we proceed if we needed to add a field to a domain entity of which snapshots have already been stored in the event store?

Coordinator
Nov 20, 2011 at 5:33 PM

In this case all snapshots of the affected aggregate type just need to be deleted.
The next time an aggregate is loaded the event store loads all correlated events from the start and rebuilds current state. 

Frankly I don't think that we will need snapshots at all. As far as I know snapshots should be considered when you have to load 100+ events per aggregate.
So I think we can start without snapshots and add them later when needed.

Coordinator
Nov 20, 2011 at 5:41 PM

BTW: snapshotting itself should not be performed after command execution but in a seperate process because it could take some time -> non-block UI .

The snapshotting process

  1. asks the event store for all aggregates (event streams) that need a new snapshot (e.g. one snapshot per 20 new events)
  2. rebuilds aggregate state with its last snapshot + new events
  3. asks the aggregate for a memento
  4. saves the memento in the event store
Coordinator
Nov 20, 2011 at 6:53 PM

I would highly recommend to NOT use snapshots unless you really need it for performance reasons. And if you really need it just use it for the aggregates you have performance problems when loading them into the current state. We made tests with 1.200 events on one aggregate and haven't had any performance problems.

I would also say that the example auf Sebastian for one snapshot per 20 events is far too less. Especially for 26th: don't use it at all.

Think in CQRS terms: you have the OPTION to use it but don't add accidentially complexity!