Closed kukeiko closed 1 year ago
Implemented interceptors:
Has been working quite well (albeit being complex to implement and figure out bugs), but needs a major change: I think I need to introduce back a context shared by all interceptors.
I remember having had that in some older implementation, probably even before the interceptors - I removed it thinking I don't need it, but I've now changed my mind. Mainly because without it, we don't have a way to load entities with hydrated relations (as currently, they are packed in mostly normalized entity payloads).
Not the best description but maybe it helps future me to remember why I am now introducing back the context.
I think now is a good time to close this issue, as I have both implemented interceptors supporting the features initially present during issue creation and added new features/adapted existing by introducing additional interceptors.
So all in all: while complex to debug, that way of implementing the logic is quite usable and serves as a good point to go forward. I do expect greater changes to it, which I should track in another, new issue.
What
Rework current loading/hydration mechanism so that I don't have to find the perfect solution right now. Do this by delegating logic to configurable components.
Why
Because supporting all the loading/hydration scenarios I can think of is a very hard thing to solve. For example, to support all hydration scenarios, we would need to be able to resolve a dependency tree (e.g. in order to hydrate A.B, we first need to hydrate A.C). There is no need to have this complex functionality right away - but what we do need is a foundation that could support it that doesn't need major changes once we introduce such logic.
How
The solution I came up with was to use the interceptor pattern.
An interceptor receives a stream and acts upon its emitted accepted, rejected, error queries as well as the payloads. It can try to fulfill any rejected queries to make them accepted, either by loading new aggregate root entities or hydrating received entities from payloads for which there is a rejection on to-be-hydrated properties.
An array of interceptors is composed and run using runInterceptors(interceptors, queries), with an array of queries describing what is to be loaded as the 2nd argument.
This way the loading/hydration mechanism is
Notes
Part 1 was #182, where I introduced usage of QueryStreams. I reused the title because it is a major rework of the same components.