Duplicating data aggregation logic in domain model and projections

186 views
Skip to first unread message

Gediminas Geigalas

unread,
Dec 13, 2016, 9:42:53 AM12/13/16
to DDD/CQRS
Hi all,

my team has started using CQRS + ES a short while ago and we're facing some problems implementing aggregation of data from events in domain and then having to use the same results in projections. Let me describe an example problem:

In basketball (yes, that is our actual domain), a player can make up to 5 fouls per game. If a player exceeds this number, he must leave the court. The total number of fouls for player must be calculated taking into account these domain events:
  • FoulRecorded - a foul has been recorded for a team
  • FoulAssigned - a foul has been assigned to a player
  • FoulUpdated - we actually allow updating many attributes at once - time, team, player. The foul might get reassigned to another player or even team.
  • FoulRemoved - foul has been removed, because it might have been recorded by mistake or was called off.
Now, the total number of fouls for a player is needed in:
  • the domain model - there is some domain logic based on the number of fouls player has
  • UI projection - we need to display the total number of fouls, apply some coloring based on that, etc.
  • integration points - there are multiple consumer systems that need to get this data
The problem that we're trying to solve is how to avoid duplicating the calculations in every place that needs it's results. The example above is one of the simplest calculations we have, but it still involves processing 4 (and even more) events to get the right results. These are some of the things that we've considered:
  • Process the domain event stream in every projection that needs the result. Accept some duplication in projection logic and the fact that they might produce different results if there are differences.
  • Provide a shared projection that is accessed by all components that need the aggregated results. This can be a shared component (class, DLL) or separate process, as needed.
  • Do the calculation in domain model, include the aggregated results in emitted domain events. For example, FoulAssigned would include the number of fouls for the player. And the team. And, in the same fashion, probably other aggregated data later as well - you get the idea. The projections become simpler, but events will include data that can be derived from the event stream already, basically duplicating information.
  • Another option is to introduce a sub-domain that will aggregate information and emit it's own events, but I'm not sure if that helps, because the results that are just aggregated from input event stream.
I'd be glad if someone could share their experience and how people solve this kind of issues. Are there some other ways to approach this?

Regards,
Gediminas.

Alexander Langer

unread,
Dec 13, 2016, 10:45:08 AM12/13/16
to ddd...@googlegroups.com
> * FoulUpdated - we actually allow updating many attributes at once -
> time, team, player. The foul might get reassigned to another player
> or even team.

If so, it might be better to make that fact explicit - FoulReassigned etc.

> * FoulRemoved - foul has been removed, because it might have been
> recorded by mistake or was called off.

FoulCalledOff? FoulMistaken?

> The problem that we're trying to solve is how to avoid duplicating the
> calculations in every place that needs it's results. The example above
> is one of the simplest calculations we have, but it still involves
> processing 4 (and even more) events to get the right results. These are
> some of the things that we've considered:

> * Process the domain event stream in every projection that needs the
> result. Accept some duplication in projection logic and the fact
> that they might produce different results if there are differences.

You can make the calculation logic an explicit strategy and use that one
strategy in all projections that compute the total number. That means
less duplication and will guarantee the same results.

> * Provide a shared projection that is accessed by all components that
> need the aggregated results. This can be a shared component (class,
> DLL) or separate process, as needed.

It the total number is only used in the UI, that is a good strategy as
well. Udi Dahan has some blogs on this topic ("UI Composition").

> * Do the calculation in domain model, include the aggregated results
> in emitted domain events. For example, FoulAssigned would include
> the number of fouls for the player. And the team. And, in the same
> fashion, probably other aggregated data later as well - you get the
> idea. The projections become simpler, but events will include data
> that can be derived from the event stream already, basically
> duplicating information.

This still has a couple of advantages, for example:

- Suppose the rules concerning calculation of fouls changes after a while.
If so, you'd need two versions of projection logic (at least if you are
using a "throw away" readmodel that you can always rebuild from the
stream):
one version for the events emitted before the rule changed and one
afterwards.
If the total number of rules fouls is recorded within the event, it's
recorded as a fact at the time of recording, i.e., at the
time the then-active rule concerning was present and you wouldn't have to
bother about future rule changes.

- The emitter of fouls is probably an aggregate, i.e., a transaction
boundary. If you add the total number of fouls to the events,
you can be sure it is consistent with the event at that version/time.
You won't be able to guarantee this with projections (or sagas, FWIW).
This might matter depending on your domain, in particular if certain
actions are to be imposed depending on, say, the total number of fouls.

(We have used both approaches in the past: a combined strategy for the
computation and in some cases enriched events, namely when we needed a
consistent "snapshot" of state of the aggregate.

João Bragança

unread,
Dec 13, 2016, 11:55:00 AM12/13/16
to ddd...@googlegroups.com
Is this for a video game? Or is this some software to aid in little league games? Basically what I'm asking is, is your system the source of truth for this information or not?

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.



--

Gediminas Geigalas

unread,
Dec 14, 2016, 3:37:46 AM12/14/16
to DDD/CQRS, joao...@braganca.name
Alexander, thanks for Your input. The comments on storing aggregated results in domain events are especially helpful, because we have quite a lot of discussions around that.

João, it's for league games.

Regards,
Gediminas.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.

Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.

Gediminas Geigalas

unread,
Dec 15, 2016, 9:05:26 AM12/15/16
to DDD/CQRS
Alexander.

>   * Do the calculation in domain model, include the aggregated results 
>     in emitted domain events. For example, FoulAssigned would include 
>     the number of fouls for the player. And the team. And, in the same 
>     fashion, probably other aggregated data later as well - you get the 
>     idea. The projections become simpler, but events will include data 
>     that can be derived from the event stream already, basically 
>     duplicating information. 

This still has a couple of advantages, for example: 

- Suppose the rules concerning calculation of fouls changes after a while. 
  If so, you'd need two versions of projection logic (at least if you are 
  using a "throw away" readmodel that you can always rebuild from the 
stream): 
  one version for the events emitted before the rule changed and one 
afterwards. 
  If the total number of rules fouls is recorded within the event, it's 
  recorded as a fact at the time of recording, i.e., at the 
  time the then-active rule concerning was present and you wouldn't have to 
  bother about future rule changes. 

- The emitter of fouls is probably an aggregate, i.e., a transaction 
  boundary.  If you add the total number of fouls to the events, 
  you can be sure it is consistent with the event at that version/time. 
  You won't be able to guarantee this with projections (or sagas, FWIW). 
  This might matter depending on your domain, in particular if certain 
  actions are to be imposed depending on, say, the total number of fouls. 

I'm wondering, if we decide to store the results in domain events, when should we choose to add new domain events. For example, when a foul is recorded, it might increase the team's foul count also. Should we introduce e.g. TeamFoulsChanged event to store that information?
I see this happening if a separate aggregate (or subdomain) is doing calculations and emitting events. But what if it's the same aggregate?

Regards,
Gediminas.

Alexander Langer

unread,
Dec 15, 2016, 9:15:52 AM12/15/16
to ddd...@googlegroups.com
Yes, if it's the same aggregate, IMO it makes sense to emit two events.

However, "TeamFoulsChanged" sounds technical, what are the actual domain
people calling that event?
> --
> You received this message because you are subscribed to the Google
> Groups "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to dddcqrs+u...@googlegroups.com
> <mailto:dddcqrs+u...@googlegroups.com>.

Peter Hageus

unread,
Dec 15, 2016, 9:21:33 AM12/15/16
to ddd...@googlegroups.com
My take on this:

If it’s anything more complicated than plus and minus, it’s in the events. The domain write model is where I concentrate the testing, and it’s also where it’s simplest to test.

The added advantage is that your projections become idempotent. as long as events are in order. This is a major benefit as well.

/Peter

> On 15 Dec 2016, at 15:15, Alexander Langer <al...@big.endian.de> wrote:
>
> Yes, if it's the same aggregate, IMO it makes sense to emit two events.
>
> However, "TeamFoulsChanged" sounds technical, what are the actual domain
> people calling that event?
>
>
>
>>
> To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.

Gediminas Geigalas

unread,
Dec 15, 2016, 10:01:02 AM12/15/16
to DDD/CQRS
The correct domain event names would probably be TeamFoulsIncreased, TeamFoulsReset, etc.

I am, however, concerned that introducing a new domain event for calculated result might also trash the domain.

This is a bit different from the original situation, where the same calculation is required in domain and projections. If the team fouls count is only used by the UI projections (at least in the foreseeable future), then I probably should start from calculating that in the projection, where it is used? Otherwise, UI or external system concerns will creep into the domain model. It also feels safe, because I'm not loosing any information by doing this. So, in such case, I would rather start out simple and wait for a real requirement to actually move that into the domain.

Does it make sense, what do You think?

Regards,
Gediminas.
Reply all
Reply to author
Forward
0 new messages