Pergunta

I am looking into Event Sourcing (ES) and having a play around with some code Greg Young put together (Greg Young Git Repo).

I like what ES offers in terms of functionality, but I am trying to apply that to my knowledge of a specific problem domain. Most applications I have worked on are all based around storing all data as state. Typically CRUD applications and a lot of DDD of late - pretty much in a SQL Server backend.

I see a lot of examples around Purchase Orders and Order Items for ES, which are pretty simplistic examples - although in my Purchase Order world we have far more complex domains - where there are all kinds of rules. Whilst these are a great starting point, I would like to broaden my horizon on slightly more complex domain scenarios.

So to test out ES in my hypothetical problem domain, suppose I have the following simplified scenario where:

Aggregate 1 - Business Configuration

Aggregate 1 is configured as a Group/Item relationship, for example:

Group 1 (Aggregate1Group)
    Item 1 (Aggregate1Item)
    Item 2 (Aggregate1Item)
Group 2 (Aggregate1Group)
    Item 3 (Aggregate1Item)
    Item 4 (Aggregate1Item)

This is typically a one of task a the user would run through, but nothing ever stays that way!

Aggregate 2 - Business Rule Set

Aggregate2 is pretty much standalone set of rules. Its user configured business rule ranges which are used within Aggregate3, but require a "reference" to Aggregate1 Group.

BusinessRule1 (BusinessRule)
    Min: 0
    Max: 100
    Aggregate1Group: Group 1

BusinessRule2 (BusinessRule)
    Min: 200
    Max: 300
    Aggregate1Group: Group 2

Aggregate 3 - Enforcing Rules

Aggregate3 requires the user to pick an item and select a value, which would be implemented with the following method:

void Aggregate3.BookThisIn(int value, Aggregate1Item item, string someIrrevantInfo)
{ 
    bool allowed = BusinessRules.Any(b => value >= b.Min && value <= b.Max && b.Aggregate1Group.Contains(item));

    if (allowed)
    {
        IrrelevantInfo.Add(someIrrevantInfo);

        // raise events
        Events.Add(new IrrelevantInfoAddedEvent(...));
    }
}

So basically, if the value is between the Business Rule Min and Max, and the specified item is within that Business Rule group, then we are allowed to log the irrelevant info.

Hypothetical scenarios and questions

So lets suppose the following events have happened:

  1. User created Groups and Items as per Aggregate 1 - Business Configuration
  2. User created Business Rules as per Aggregate 2 - Business Rule Set
  3. User BookThisIn for value of 50 and Aggregate1Item of Item 1 with someIrrevantInfo of "Some Test"

All domain data is in a valid state so far... Or is it?

Now the user decides they got the configuration wrong. Aggregate1Item "Item 1" actually belongs to Aggregate1Group "Group 2". Given that, the data entered in step 3 is now in fact incorrect.

Potentially, the data could be determined to be historically correct in other cases, where all current data is deemed correct at the point in time events occur.

So in the case of a state based SQL database, you could look at any entities stored in the IrrelevantInfo list (that would map onto a table - say IrrelevantInfos) and you could make the user resolve the issues within a UI.

But in an event based system, how would you play out this invalidation scenario - and how could you provide the information to the user to resolve? What patterns does ES offer to overcome this hurdles?

Foi útil?

Solução

What you have here is inter-aggregate validation, possibly inter-bounded-context also. This is not a kind of validation that is done inside a single Aggregate, so this code && b.Aggregate1Group.Contains(item) inside Aggregate3 is not permitted and should be moved inside a Domain service, i.e. DS1 (naming is very important in DDD so give it a proper name).

You can call DS1 in your Application service, before the call to Aggregate3 if you want to minimize the time that the system as a hole will enter in an invalid state. Note that the calling of the DS1 is outside the Aggregate3 so it is not strongly consistent with the Aggregate3.

But, as you already noticed, users will change their mind about the Aggregate1 group state so the system will enter an invalid state (an Aggregate never enters an invalid state, by definition).

The answer to this problem is resolved by a Saga/Process manager. So you need to create a Saga that calls the DS1. If it detects an invalid state, the Saga takes the required actions. What action is that it depends on your system. It could be sending an email to somebody, showing a notification on the UI, sending a command to another Aggregate or all the above. The Saga may have services injected so it can have any side effects.

Event sourcing makes easy the invocation of the Saga: the Saga subscribes to the relevant domain events. Also, ES makes the detection of the invalid state as early as possible (typically milliseconds - depending on the infrastructure).

So, the answer is given by DDD, Event sourcing just makes it easier to implement.

Licenciado em: CC-BY-SA com atribuição
scroll top