QuietNaN QuietNaN - 4 months ago 49
C# Question

Using Domain Events to Update Entity in Database

I have a PurchaseOrder aggregate root which has Two methods FinalizeOrder and CancellOrder, they both record events: OrderFinaziled and OrderCancelled. I am stuck on modeling order repository, can i use those events inside repository pattern to update entity in database? i don't wont after each change to save whole aggregate root, i want to save only the field that was changed, i am using SqlClient, no ORMs.

my Aggregate root base class:

public class AggregateRootBase<TID> : EntityBase<TID>
{
public AggregateRootBase(TID id) : base(id)
{
}

private readonly List<IDomainEvent> recordedEvents = new List<IDomainEvent>();

public IEnumerable<IDomainEvent> GetEvents()
{
return recordedEvents;
}
public void MarkEventsAsProcessed()
{
recordedEvents.Clear();
}

protected void RecordEvent(IDomainEvent @event)
{
recordedEvents.Add(@event);
}
}


PurchaseOrder class (skipped most properties):

public class PurchaseOrder : AggregateRootBase<int>
{
public PurchaseOrder(int id) : base(id)
{
IsFinalized = false;
IsCancelled = false;
}
public bool IsFinalized { get; set; }
public bool IsCancelled { get; set; }

public void FinalizeOrder()
{
IsFinalized = true;
RecordEvent(new OrderFinalized(Id,IsFinalized));
}
public void CancellOrder()
{
IsCancelled = true;
RecordEvent(new OrderCancelled(Id,IsCancelled));
}
}


and repository:

public class PurchaseOrderRepository
{
void Save(PurchaseOrder purchaseOrder)
{
var events = purchaseOrder.GetEvents();
foreach (var evt in events)
{
if(evt.GetType() == typeof(OrderFinalized))
// use event args and update field using SqlCommand
else if (evt.GetType() == typeof(OrderCancelled))
// use event args and Update field Using SqlCommand
}
}
}


i also have an EventDispatcher which dispatches events (Email Notification) after AggregateRoot successful persistence.

Answer

If you want to use domain events to save your changes, you are talking about projecting events to your aggregate state. There are some tools that can help with that. What you are trying to do resembles the pattern matching, a usual feature of most functional languages. To make life easier you might want to check something like Projac. We use it for projections and it works very nicely. It also has SQL Server specific implementation.

One example that you can find there:

public class PortfolioProjection : SqlProjection
{
  public PortfolioProjection()
  {
    When<PortfolioAdded>(@event =>
      TSql.NonQueryStatement(
        "INSERT INTO [Portfolio] (Id, Name) VALUES (@P1, @P2)",
        new { P1 = TSql.Int(@event.Id), P2 = TSql.NVarChar(@event.Name, 40) }
    ));

    When<PortfolioRemoved>(@event =>
      TSql.NonQueryStatement(
        "DELETE FROM [Portfolio] WHERE Id = @P1",
        new { P1 = TSql.Int(@event.Id) }
    ));

    When<PortfolioRenamed>(@event =>
      TSql.NonQueryStatement(
        "UPDATE [Portfolio] SET Name = @P2 WHERE Id = @P1",
        new { P1 = TSql.Int(@event.Id), P2 = TSql.NVarChar(@event.Name, 40) }
    ));
  }
}

Then you can initialise projector instance:

_projector = new SqlProjector(
   Resolve.WhenEqualToHandlerMessageType(new PortfolioProjection()),
   new TransactionalSqlCommandExecutor(
     new ConnectionStringSettings(
       "projac",
        @"Data Source=(localdb)\ProjectsV12;Initial Catalog=ProjacUsage;Integrated Security=SSPI;",
        "System.Data.SqlClient"),
      IsolationLevel.ReadCommitted));

And then project events:

void Save(PurchaseOrder purchaseOrder)
{
    _projector.Project(purchaseOrder.GetEvents());
}

You might also check the event sourcing pattern although it adds quite lot of complexity.