> * 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.