Pergunta

I'm implementing a bounded context using event sourcing but have come across a problem. Say I'm modelling a game of soccer, and I'm interested in both the individual goals scored (who scored them etc) and the overall score. So if I have a Match aggregate root I ideally want events raised called GoalScored and ScoreChanged. The reason I want the score explicitly stated from the domain like this is that I don't want lots of different listeners and possibly other bounded contexts all computing the same thing.

This seems simple, but: the Match object has a Goal() method that adds a new goal. In the spirit of event sourcing this doesn't directly mutate Match state, but raises a GoalScored event that is handled within the Match which then mutates the state (as well as the event being pushed out to denormalizers). So in terms of raising a ScoreChanged, the score hasn't actually changed until the GoalScored event has been handled, so do I raise another event in response to that event (ScoreChanged), effectively chaining the events? I don't think so, for a start when an aggregate root is reloaded from the event store lots of extra events are going to be created each time in response to each GoalScored.

I also thought about working out what the score would in the command handler that raises GoalScored, sort of a 'what if' situation. Then I could raise both events in the command handler. I'd really rather not do that though - it just doesn't seem 'right'. Working out the score is simple enough for soccer, but other games (cricket for example) requires more work.

I could put both the goal and the score in the GoalScored event, which is fair enough, but again it doesn't seem right - the score has got nothing to do with a GoalScored event per se.

All the examples used when discussing event sourcing seem to use the eCommerce Customer/Order domain, and I've never seen a similar case as this.

Does anyone have any experience dealing with situations like this?

Thanks

Foi útil?

Solução

The choosing of clean domain events, just like other modeling, should result in concepts that are mirrored in the domain. You say "the score has got nothing to do with a GoalScored event per se". But it does. In soccer, the only way a score can change is if a goal is scored. Though, goals can be taken back, e.g. via offsides calls or other penalties. It's not clear whether you want to model this or not.

It is common for a domain method to emit multiple events at once. A good framework will make it easy to consider them as a bunch, e.g. as a single commit. Why not emit both a GoalScored and ScoreChanged event?

You may also want to consider whether this domain has any commands at all. The soccer match itself is the system of record. The events that come from the match are a record of history already. Are you perhaps just writing a system which processes streams of events into streams of more events?

Outras dicas

One thing that often helps when thinking about event sourcing is to notice your tense - you say StartMatch, but in an event sense it's actually the event MatchStarted.

As for the ScoreChanged, are you consuming this event outside of the Match? If not, the Score should be the only part publically accessible, and the GoalScored event simply mutates this. This holds CQRS in a minor way (how I get the score doesn't depend on how I change it). Score can then internally hold a state, or can replay all the GoalScored events to arrive at a number each time it's called. The 'feel' of properly designed event-sourced solutions can always regenerate any state from the events.

Now, if the ScoreChanged needs to alert other parts of your system (a player ranking, for instance), you can either multi-cast the event to different roots, or you might need to refactor your design. In this case, do you want to update players in real time, or only after a match is complete?

I have this open source project for event sourcing in scala which contains examples of how a basket works using it. Just in case is useful for you https://github.com/politrons/Scalaydrated

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top