Search code examples
c#domain-driven-designevent-sourcingneventstore

Event Source, Anti-Corruption layer design with NEventStore


I have two legacy enterprise application that have a few similar features. I need to build a system that responds to data change events from those systems, processes that data and exposes the combined results through an API in multiple formats. I would like to use an Event Source/DDD style architecture but I'm not sure if it makes sense. Given the simplified model below, how could I design the system?

Note - The following has been edited to clarify the question:

Both systems have Products that contain different prices based on the date. When the price changes, each system can emit an event, PriceChanged, that contains the identifier of the legacy system, that system's primary key, a date and the new price. The product ID is unique to the system, but may not be unique between both systems so a system ID will also be included.

    PriceUpdated {
       int systemId,
       int productId,
       Date date,
       Float amount
    }

Within the bounded context of my new system there would be a service that receives this event and need to look up my aggregate by systemId, productId, and date and then emit a command to update the corresponding price in the aggregate. My aggregate would be defined as:

class ProductPriceAggregate
{
   Guid Id,
   int systemId,
   int productId,
   Date date,
   Float amount

   Apply(CreateProductPriceCommand e){
     Id = e.Id;
     systemId = e.systemId;
     productId = e.productId;
     date = e.date;
     RaiseEvent(new ProductPriceCreatedEvent(this))
   }

   Apply(UpdateProductPriceCommand d){
     amount = e.amount;
     RaiseEvent(new ProductPriceUpdatedEvent(this));
   }
}

If I am using NEventStore which stores streams using a GUID, then each aggreateId will be represented by a GUID. My service would need to query for the GUID using the systemId, productId and date to emit a command with the correct ID.

The service might look like this:

class PriceUpdateService : ISubscribeTo<PriceUpdated>{
  Handle<PriceUpdated>(PriceUpdated e)
  {
    var aggregateId = RetrieveId(e.systemId, e.productId, e.date);
      if (aggregateId == null)
        Raise(new CreateProductPriceCommand(e))
      else
        Raise(new UpdateProductPriceCommand(aggregateId, e.amount);
   }

   RetrieveId(int systemId, int productId, DateTime date)
   {
      // ???
   }
}

The question is what is the best way to look up the aggregate's ID? The legacy systems emitting the PriceUpdated event will have no knowledge of this new system. Could I use a read model that is updated in response to ProductPriceCreatedEvent that contains enough information to query for the ID? Would I need another aggregate who's responsibility is to index ProductPrices? As posted as an answer by VoiceOfUnreason, I could use a repeatable convention for generating the ID by systemId, productId and date. Is this the recommended option from a DDD perspective?


Solution

  • Do you control your own IDs?

    PriceUpdated {
       int systemId,
       int productId,
       Date date,
       Float amount
    }
    

    An alternative to trying to lookup an aggregateId is to calculate what that aggregateId must be. The basic idea is that the different points that need to find an aggregate from this event share an instance of a Domain Service that encapsulates the calculation.

    The signature looks like the query you wrote in your question

    // Just a query, we aren't changing state anywhere.
    var aggregateId = idGenerator.getId(e.systemId, e.productId, e.date);
    

    Any given implementation takes it's own salt, the arguments you pass to it, and generates a hash that is the common id used everywhere to map this combination of arguments to an aggregate.

    You can, of course, produce identifiers for different aggregates using the same event data by passing in an idGenerator with a different salt.

    For the particular case where your IDs are UUID, you can use a Name-Based UUID.