Hi Ryan,
Well explained question. I believe I’ve asked such a question myself in the past.
I too read the advice “don’t use ES Projections to materialise your aggregates” and I also questioned “why not?”
And I still don’t have a definitive answer.
However, a projection will NOT give you:
-
control over how you ‘snapshot’ your state.
-
a means of skipping an event if a subsequent event has marked it as cancelled (which is one way of dealing with correcting history).
-
control of what history to replay in the event of resetting a projection (especially if it emits to other streams).
-
any choice as to what language to use - just JavaScript.
-
any assist in reusing projection code, as there is no import/load feature that seems to work.
Also, a projection is very unforgiving if its emitted streams are written to by anything else, or if its emitted stream is deleted. So recovering from some issues may require streams to be soft-deleted and projections reset, or even to use new stream names!
That will not be an exhaustive list as I still consider myself an event-sourcing newbie.
There is no single right answer as to how to build a solution from a set of tools and platforms. I think the most important thing is that you have an understanding of the strengths, weaknesses, and risks - i.e. a good awareness of what you don’t know. Then you can make informed decisions whether other people like them or not, and whether or not they turn out to be a ‘good’ decision - which might even be a bad fit but which leads to some positive learning (clouds and silver linings).
So, with all that in mind, the path I took was…
-
I AM using ES Projections to materialise my aggregates.
-
categorised event stream per aggregate.
-
projection per category.
-
my projections emit the materialised aggregate to new streams.
-
I subscribe to my aggregated streams and load the emitted state into MongoDB, overwriting any previous state.
-
when correcting history I have to take that aggregate offline, rewrite the entire event stream ‘disabling’ (not deleting) any event previous erroneous event, and inserting corrected ones as necessary.
-
my downstream aggregate materialisation subscribers have to be idempotent enough to cope with “Groundhog Day” (projection resets).
Furthermore, I have come up with a (suits me) TypeScript approach which lets me…
-
specify versions of Commands and Events.
-
provide revisions of handlers which materialise my evolving events (multiple handlers may be required to cover the full history of how an event has evolved).
-
combine revisions of handlers to form a versioned aggregate.
-
allow for multiple materialisations of the same aggregate event stream, into multiple emitted aggregate streams of course.
This TypeScript approach has meant that I can…
-
create type-safe event handling code.
-
re-use projection code (my handlers) because tsc (TypeScript compiler) will produce me a single JavaScript file irrespective of the sources.
-
load my resultant JS projection (single JS block from multiple TS sources) into ES via HTTP API.
-
support API versioning at both ends of my Materialisation pipeline (versioned events lead to versioned aggregates - but the handlers in-between can either translate breaking changes or soak them up thereby protecting one end from chage).
-
feed into templated generation of further server-side (Node) or client-side (browser) code.
So far, I have found this to work well for me, but my work has been largely R&D and I have not produced a production system with this yet.
And whether or not others would agree with this approach remains to be heard. I suspect that many would shoot this down in flames. But does that mean it wouldn’t work for me? Or just that it wouldn’t work for them?? Or just that they think it wouldn’t work for them???
I wish that my TypeScript-driven projection generator was further along so that I could share it with you, but it is still only a rig for lab work.
This is still not an answer to your/our question, but I hope it fuels the discussion in a helpful way. I for one would be very happy to discuss further.
Cheers,
Raith