1

I'll first explain the context of my problem. The backend of my current project is using DDD, CQRS and Event Sourcing as a stack (the read DB being a MongoDB, my write DB being an event store).

At some point I had to chain multiple commands in a "transactional" manner, did some research and had to choose between listening to events or implement them using a saga, I decided to go with listeners.

So the flow is as following: Command A is launched, a handler catches it, get's the aggregate corresponding the command, aggregate does his business logic which generates Event A, then aggregate is persisted to the event store (the generated event is stored in a list of uncommitted events in the aggregate, and the repository only persists those events).

So when event A is persisted it's added to the event stream and a handler listens to all the events coming there and a message broker recreates the event and publishes it for potential listeners.

A listener catches Event A, and creates a Command B according to information in Event A, which then sends an Event B, and so on.

My read side is synchronized in the same manner, there are listeners which listens to the events sent by the broker, and the read models are populated / adapted.

So here is the actual problem, this all works perfectly, but there is 1 catch here. Let say I want to recreate my read DB, i drop it, relaunch it, all the events are re-applied in the right sequence, my read model listeners catches the events and apply it to my model, fine, but the problem is that my write side listeners also listen to those incoming events, which is problematic because this causes a command to be launched while.

A solution would be to implement sagas, which will remove the write side listeners, but I really like this approach so I'd like sagas to be a last resort option, but I can't seem to wrap my head around how to actually split those 2 types of listeners, so any ideas ?

Sorry for the long read, but I had to explain it as a whole picture to get good suggestions.

  • It seems you need a mechanism to detect if an event should be handled or if it already was handled previously. Do you have timestamps in your events? – Rik D Apr 8 at 7:59
2

Read and Write listeners have a different life-cycle.

  • Write listeners are not rerun over old data. They are run once. (If they are idempotent you might be able to rerun them but that is just duplicating work and increasing risk.)

  • Read listeners are rerun over old data in order to reconstruct views for historic purposes or to rebuild a living reference.

The simple solution is to split the read and write listeners into two separate channels.

The first channel holds all of the writers, the second channel all of the readers.

Now the readers channel can be resumed at any historic point, and progressed to an specific point in time.

It might even pay to have several reader channels with separate groups of listeners for separate isolated views.

  • Yes, that's actually quite a good idea, that means I'll have 2 streams for my aggregate then ( or even 3, 1 for all events, and a handler that subs to all events and write it to 2 separate streams, on for read, one for write ). It's actually quite simple and straight forward, shame I didn't think of it, guess I was too deep in it, thank you sir ! – TanguyB Apr 8 at 8:13
  • Isn't "write listeners are run once" sagas in disguise? – herby Apr 8 at 17:31
  • @herby And a saga is just a workflow in disguise. Its the same trick just a different level of resolution. The Listeners are an implementation detail, they are how the system responds to an event. One level up and you would see the each listener is just one stage in a Saga representing who does what. Zoom out again and you see that what you have is a Workflow which says what must get done. But for this case it doesn't mater, we are dealing with a cross-cutting concern of global-state change vs global-state observation. Here listeners is probably the better level of detail. – Kain0_0 Apr 8 at 23:07
0

You should focus your attention on the domain. Your question suggests that maybe you have missed an important concept in your domain: a business process.

Business processes are very important, at the same level or higher as the Aggregates. They should be first class citizens. They should stand bold in your code. It should be very clear where a business process starts, where it ends and what Aggregates coordinates.

That being said, go ahead with Sagas and implement this concept as a Saga. You would most probable use them again in your model.

As a low level note: Sagas listen to events just as Readmodels but you need to attach to them a Tracker of some sort that will help them to ignore duplicate events. This Tracker would persist to some database the IDs of the processed events and would use this persistence to check if an event has already been processed by the Saga.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.