Component encapsulation by default
See original GitHub issueMy main issue with the current Cycle architecture is the way drivers are actually globally scoped:
DOM.select('.field')
looks for an element in the whole DOM subtree managed by CycleHTTP
is the stream of all HTTP requests/responses going through the driver
The developer has to be aware of this and take extra precautions to filter out other inputs, such as other parts of the DOM also matching the selector, or other requests issued by other bits of the app.
The HTTP and fetch drivers encourage filtering the stream of requests by URL or name, but that solution is suboptimal because it could easily suffer from clashes if other components requested the same URL or used the same identifying name. Put otherwise, there is no strong guarantee of isolation.
The better, driver-agnostic solution to this problem is isolate (provided the driver implements isolateSource
/isolateSink
– the fetch-driver doesn’t for example).
It could be argued the implementation of isolateSource
/isolateSink
is somewhat inelegant compared to the rest of Cycle, with the HTTP driver patching a _namespace
property on the request, and the DOM driver writing class names that end up in the DOM (see https://github.com/cyclejs/core/issues/226), but I guess these are mostly hidden away from users.
However, shouldn’t isolation be default behaviour, possibly with an opt-out, rather than being opt-in?
Anecdotally, of all the people I talked to this about, no-one had realised that sources.DOM.select(_)
was not scoped to the virtual-dom subtree returned by that component.
I really love the concept of fractal architecture @staltz is striving for, but I would argue one of the key benefits is to reduce cognitive load and complexity for the developer by allowing local reasoning, without side-effects. Without isolation, there’s a persistent risk of global “side-effect” (in the broad, non-FP sense) and hard-to-debug collisions between distinct parts of the system.
Another way to phrase this is: when is isolation not desirable? Surely, components should always be isolated, unless they rely on side-effects (watching DOM they don’t own, catching requests they didn’t issue), which is antithetical to the whole concept.
These practical concerns aside, I’m also curious about the performance and scalability implications of having such centralised management of streams. Are there benchmarks of how this scales up to thousands of components, with hundreds of subscribers to the HTTP driver requests stream and thousands to the DOM driver events streams, recursively calling the isolateSource
and matchesSelector
for each level of the component tree?
I’d like to make it clear that I still think Cycle is a fantastic idea, and the reason I raise this is because I’m worried this could get in the way of its success.
To make this a more constructive criticism, I’ve also played with an alternative DOM driver API that keeps effects local. I’ve put it up in a repo on GitHub, in the hope it can spur discussions on alternative solutions to this issue.
I admit I was also trying to make observed events more explicit in the component sinks, rather than relying on .events()
on the sources and manually matching class names in the .select(_)
and outputs. However I’m interested to hear what people think, what principle I have violated doing so, and what may be wrong with this approach 😄
/cc @jamesgorrie @kenoir @phamann @oliverjash @jackfranklin who I discussed this with in the past few weeks.
Issue Analytics
- State:
- Created 8 years ago
- Reactions:1
- Comments:89 (58 by maintainers)
Top GitHub Comments
The fact that it is lazily attached to the root is implementation detail. If we would have attached it eagerly on the root when the dom driver is created ( so not anymore on the first call of select().events()), it wouldn’t make any difference at all for the application developer.
The point here is: conceptually this is an entirely harmless side effect and I have no idea why we discussing this as a problem. It is not.
Now we just lost an important property.
Because now we have read effects (click listening) encoded in the sinks. and this is a real problem because the virtual DOM rendering isn’t anymore just about rendering a visual output to the user, it is also about programming what happens when the user interacts (read effects). In practice if I want to code the users interaction, I should only write code in the
intent
, which determines what events are interesting. But in your suggestion, now those"interesting events" (e.g. click) are encoded in the view. It undermines the foundation.Yes, I agree on that, and I remember it being one of the motivations for keeping it like it is. Isolation is primarily between siblings. The parent technically should have control and visibility over everything that comes in and goes out.