8 ms·
Avoiding Event Chains in Single Page Applications
- illumen 12y agoComplex.
- sehr 12y agoHow? Everything about this seems incredibly straightforward
- illumen 12y agoCall me old fashioned, but function calls are pretty nice events. Rather than heaps of code, and weird concepts, you can just implement firing the event into the A, and B models by calling a method on each. Simple! A.getFilteredDays(payload); B.getFilteredDays(payload); That's only two lines of code compared to 30 or so. This scales up because with more models, you have less code. In practice these are easier to debug, because the debugger supports function calls with stack traces, and easier to read, because people understand function calls. They are also faster. This is why I think that long article is complex. Remember that OO uses messages and objects. Method calls are events. Also remember that there is an M in Document Object Model (DOM). So you see we already have a Model, and events? Please also consider how much less code there is in the jQuery version of the todomvc app compared to the react one. http://todomvc.com/ http://todomvc.com/
- swah 12y agoAre you saying there is a chance we go back to jQuery as the superior solution? :)
- coldtea 12y agoHmm, this answer seems to have missed 7-8 years of the web's development, and shows a preference for convoluted, messy codebases with huge conginitive overload...
- illumen 12y agoExplain how smaller, simpler and faster code is worse.
- coldtea 12y agoSmaller: Not really. You just avoid some K of helper code already build (by the React team). Your code will end up being more than the one you'd have written on top of React -- because it will also have to reimplement and handle all common cases (either poorly, or time-consumingly). Faster: could be, could be not. Without profiling this is an empty statement. Besides, "premature optimization is the root of all evil". Simpler: Noope. You have to manually track all the interactions, and you strongly couple things together with your "OO and functions" idea. It might be simpler to just churn out code initially (instead of understanding an architecture), but it becomes an ad-hoc mess soon. Let me put it this way: it's not like you have discovered a novel way of building stuff compared to these coders that overcomplicate things. What you propose is what these engineers have already tried, used for a decade or so, and found unscalable and wanting. It can work for small and not complicated pages, but it's not a solution to modern single page web apps. You might think that you are proposing a clear and simple way of coding as opposed to something like overengineered J2EE patterns mess. But what you describe is more like the "Why use procedural code etc, GOTOs for flow are simpler and faster", or "why use functional programming, imperative is simpler and faster".
- illumen 12y agoThe code I mentioned is definitely smaller than in the article. It is faster, because it is just a function call. It doesn't create any event objects, or have to go through six layers of other function calls. It's simpler because there is less code, and there are less concepts. No, it's not novel or new. Messages as function calls, and MVC comes from 70s smalltalk. It's not strongly coupled because of a few reasons. Firstly because in JavaScript it is simple to dynamically reassign objects and functions. The receiving object does not care where it got the function called from. Only the sending object knows where it is sending to. However, from Coupling theory in Software Engineering, we know that if there are a small amount of outputs then the strength of the coupling is still low. Luckily in many apps there are often only a few places where you want to send a message. http://en.wikipedia.org/wiki/Coupling_%28computer_programming%29#Module_coupling http://en.wikipedia.org/wiki/Coupling_%28computer_programmin... Yes, if there are lots of outputs it may be useful to reduce the coupling. In my experience, even with large apps (100 person teams), this is often not the case. It is often useful to know what your app is doing by looking at where the events are going. Just using function calls makes this very easy, both in source code and in the debugger. So this also needs to also be weighed up in the decision of how you are passing events around.
- kaonashi 12y agoOh boy, jQuery soup. Sounds fun to unit test.
- illumen 12y agoUnit testing has been done with jQuery since it came out in 2006. Rather than one giant monolithic app, jQuery promotes putting things into plugins or separate libraries. What stops you from unit testing function calls? Nothing.
- kaonashi 12y agoIt also promotes procedural code for modifying DOM, not declarative. This makes unit testing much harder imo, since you're into the realm of either working against a real DOM, or mocking everything.
- illumen 12y agoIn the example from the article we are talking about, it would be easy to mock out the one function call for modules A, and B. There's no DOM manipulation in the article. Keeping DOM modifying code separate is good practice in either case. Then you don't need to involve the DOM in much of your code. Doing end to end testing is also completely ok to do. It's worth repeating, so I'll say it again... It's better to keep DOM manipulation code separate. Then you don't need to mock, or simulate anything. Except where needed. You do need to test DOM manipulating code somehow. AngularJS also uses the DOM in their tests: https://github.com/angular/angular.js/blob/master/test/ngAnimate/animateSpec.js https://github.com/angular/angular.js/blob/master/test/ngAni... Here's a jquery-ui test for comparison: https://github.com/jquery/jquery-ui/blob/master/tests/unit/slider/slider_core.js https://github.com/jquery/jquery-ui/blob/master/tests/unit/s...
- kaonashi 12y agoWith React there really is no notion of manipulating DOM, so there is no need to test it.
- toomim 12y agoI'm with you, bub. The people arguing against you don't know what they're talking about. They're stuck in a paradigm where "de-coupled event-driven architecture" is a holy goal and can't see their feet on the ground anymore.
- Kiro 12y agoOT but I'm looking for a comparison between Angular and React/Flux, preferably with a real case study. Working with templates that update themselves without having to bother with anything else just seems to much easier...
- papaf 12y agoI have had lots of problems trying to understand and use Angular. Most people don't. The TODO MVC site has comparisons for a simple app: http://todomvc.com/ http://todomvc.com/ The main problem is that a TODO app is too simple. But if the app were more complicated it would be much harder to prepare and read the examples.
- noelwelsh 12y agoMy comparison is here: http://noelwelsh.com/programming/2014/08/17/angularjs-vs-react/ http://noelwelsh.com/programming/2014/08/17/angularjs-vs-rea... It isn't a 100% fit for what you're looking for -- there is no case study -- but it might be useful to you.
- Retozi 12y agoI do not have much experience with Angular, however, I feel the arguments over different frameworks depend a lot on the specific nature of your app. two-way databinding is perfectly fine and really helpful for a lot of apps. However, it will screw you for certain use cases. In my opinion, it really boils down to how much global interaction you have in your app. If you simply render a lot of models that can only be changed trough the view, then I don't really see a reason for a Flux like architecture. Two-way databinding is perfectly fine there. (React vs whatever then boils down to performance vs toolset) If you do a lot of graphical stuff that allows for heavy interaction, filtering and other kinds of stuff, I believe that two-way databinding will be your downfall. You need to clearly and explicitly map out on how data flows through your app. That is the time where an architecture like Flux shines. So ask yourself this question: "How often can a user do something in your app, and a lot of Views need to rerender?" If the answer is "often", then you should consider something like Flux. If not, Angluar might be a better fit, especially if you already know it.
- gcb4 12y agoit's amazing how JavaScript is becoming the new java. everyone thinks that adding a ton of extra complexity in the guise of a simpler api will make it more maintainable. in this example you now have 4 events under the hood and two event dispatchers. but it seem simpler because you only "see" two and one dispatcher. while it may make things just a little bit simpler to maintain you may enter debug hell when there's bugs on that added under the hood complexity... which will often be a code you're unfamiliar. making the article main argument (helping on debugging edge case bugs) kind of two edged
- mateuszf 12y agoThe thing is that the hidden complexity will not appear, as rendering and state management are now separate. Both can be unit tested in separation. Components are modular and composable.
- Retozi 12y agowriter of the article here: I simply cannot follow the argument about "added complexity" The use case defines the minimum amount of links you have to make, in this case 4. Now you have two options: You just roll with it (we used to do that), or you try to abstract away a few steps (we do this now with the dispatcher). The dispatcher here is roughly 100 lines of fairly simple, unittestable code, so I really cannot see where the elusive bugs come from. 100 lines that you will easily save if your app is complex enough by the way. With the dispatcher, you can abstract away a couple of steps consistenly over your app. Now I fully agree that you need a certain threshold of complexity to make it worthwile. But you cannot avoid the original complexity of your usercase. There is no alternative to decent abstraction if you reach a certain level of complexity, because it is given externally. Other concepts like two-way databinding do exactly the same.
- userbinator 12y agoIt seems all you've done is inverted and hidden the sequence dependency in a series of waitFor chains instead of event chains, which in some ways is worse; compare A: update; notify B; B: update; notify C; C: update; notify D; D: update; with D: wait for C; update; C: wait for B; update; B: wait for A; update; A: update; The former is a clear sequential path, the latter builds up and then unwinds in a stack-like fashion. It's like the difference been non-tail and tail-calls. It's only a technical nuisance that one model needs to wait for the other to update. I would certainly not view a sequence dependency that's critical to correct operation as a "technical nuisance" to be hidden away; it's an important fact.
- rtpg 12y agoI've read this 3 times and I still can't see what the advantage of this dispatcher is. In the event model, Events that deal with model A also have to be dealt with in models B,C, and D. Wouldn't this lead to exponential explosion? The fact that B has to explicitly wait for A in the code also seems prone for errors. If A changes to no longer deal with that event, will B block forever? And this still builds an event chain, because B is blocking on A. I don't see how this is different from an event chain, execpt that the dependency is non-obvious (whereas a callback chain/future chain will at least show what data is being passed into B from A) This is maybe OT as well, but this article could really do for some concrete examples in the explanations. "Model A" and "Model B" are not examples.
- DougBTX 12y ago> If A changes to no longer deal with that event, will B block forever? No, A would just no-op, and control will return to B. This is all written in the context of JavaScript, with a synchronous dispatcher and no threads which can block.
- andybak 12y agoIsn't it simply a case of 'spaghetti code' vs 'explicit and clear code'? The dispatcher takes a potential mess of calls between various components, centralizes them and makes the sequence of events and dependencies much easier to reason about.
- msy 12y agoIt took me a while to come round to it but this kind of madness is something that Ember's event/render loop and implicit property dependency graph makes really, really easy to solve. People knock Ember for its complexity but in reality it's simply a complete/coherent solution for the kind of problems people like this are iterating towards - and watching their code get more complex as they do.
- DougBTX 12y ago> implicit property dependency graph How similar is Ember's dependency tracking to Knockout's? With Knockout I've had exactly the problem described in the article, where properties end up depending on eachother in a chain, and it is hard to keep track of what actually happens when data changes across multiple view models, particularly when inevitable special cases appear, where really I want to do something slightly different half way down the chain depending on what initiated the change, but all I have is a generic "property changed" event. How does Ember handle that?
- kaonashi 12y agoHaving built large Ember and Flux apps, Ember seems a big opaque mess. Computed properties which depend upon other computed properties never seem to update quite right. The object system is tightly coupled to the template system. It's a monolithic framework that depends upon hidden magic to bind the pieces together.
- jokull 12y agoComing from Backbone.js so this post was extremely helpful to me. Thank you. I do recognize the exploding complexity of event chains.
- benaston 12y agoYou may also be interested in an open source library I wrote called EventAPI https://github.com/benaston/event-api https://github.com/benaston/event-api
- lukasm 12y agoDoes anyone know how this dispatcher is different from WPF dispatcher? Maybe that's the way to sell it.
- rational-future 12y agoIMHO WPF doesn't have so much of a good reputation, to be used to market other products.
- lukasm 12y agonot for marketing per se, but to sell to understand what is it and why is it good. A lot of people know WPF and you could use the analogy.
- regularfry 12y agoIt looks to me like the case where this is required is actually screaming out for a new event type, not fixed dependency order. B shouldn't be listening for the same event as A is if B depends on A getting something done. A should be firing a new event which B can listen for. Is there some subtlety I've missed here?
- Retozi 12y agoThe flaw with such a strategy is that it does not scale. (We used to do exactly what you described). In a big "eventful" app, you will have way too many events to keep track, and you will constantly be browsing trough different modules to understand the chain. Lets assume the user clicks somewhere, so an event is fired "user-clicked-x" that model A listens to. So you know that model B needs to change as well and you fire a new event type from model A, say "model-a-changed-because-of-user-clicked-x" or a more generic event, say "model-a-changed". Both will cause headaches, because they require a quite high cognitive load. With the generic one, you have the issue that sometimes you want A to change but not B, which leads to lots of conditionals (all of which you need to remember). With the specific one, you have two events that mean almost the same, but are different. With every model C and D you need to carefully evaluate which event you listen to. In isolation, this is perfectly fine.But if you have 10+ of those things interacting with eachother, it will be a big mess. You will have 30-40 different events, that map out a hierarchical order that you need to track down through multiple modules every time you change something. If you do not abstract the chain away, the complexity is simply too high. You'd have to keep roughly 15-20 links in your head, which is too much. With Flux or similar patterns, you just have to keep one pattern in your head. In the end it's a simple story of abstraction. Your mind can only deal with a couple of different things. If you reach the threshold, you need to abstract. Flux shows one way to do this (and it is only useful if you've reached the need for abstraction threshold). Having one or two short event chains is perfectly fine. If you have 5 different user interactions that have a complex ripple effect through the state of your app, you need to do something, or development speed slows.
- regularfry 12y agoThis I can well believe, but I'm interested in this bit: > you fire a new event type from model A, say "model-a-changed-because-of-user-clicked-x" or a more generic event, say "model-a-changed". These two seem to be on opposite ends of a spectrum where I'd try to pick a middle point. I wouldn't want the semantics of "this is a reaction to a UI event" anywhere past the first event, it's way too detailed. I'd try to pick something like "model A's date field changed ", or ideally something more meaningful like "The User updated their address".
- swah 12y ago"But when somebody uses his mouse to click somewhere and expects something to change, things might get tough. If you're lucky, it's just a local action, like a dropdown menu that needs to open. If you're unlucky, it's some complex filter action on your data heavy app that causes three ajax requests and changes to seven models." Reading this I start to think if isolated javascript components and a jquery pubsub system publishing the 'newclick' event from the server isn't simpler and more modular..
- rational-future 12y agoSo Flux is poor man's BizTalk.
- coldtea 12y agoYes -- but only if you half-understood Flux, didn't read the article, and just wanted to make a snark comment.
- rational-future 12y agoNow that's a reassuring yes :) What I meant is it seems (I've never used or seen it before) to be closer to a message broker than a bus - http://www.udidahan.com/2011/03/24/bus-and-broker-pubsub-differences/ http://www.udidahan.com/2011/03/24/bus-and-broker-pubsub-dif...
- MattBnB 12y agodisappointing...thought you meant F.lux
- Illniyar 12y agoIf ModelB needs to wait for ModelA, and it already is aware of ModelA (which your code requires) - why not make ModelA into an eventDispatcher, have ModelB listen to ModelA, and have ModelA dispatch an event when it's done (Or write a small onX function in ModelA). It's also clearer, easier to implement, already implemented in tons of frameworks and easier to unit test (since you no longer need to mock the appDispatcher). You should probably try to avoid using a global eventDispatcher (as any other global), whether you can specify order of execution or not.
- akrymski 12y agoA model listening to other models? Yuck, that's just wrong. In an MVC application, only controllers should update models. Just because Backbone lets you listen to model events from anywhere doesn't mean it's a good idea. IMHO Application events should simply bubble up through the DOM, and get caught by appropriate controllers (Backbone.View class in backbone parlance) that then modify the appropriate models. If need be it's the top-level controller for the app, eg: document.body.addEventListener(function(e) { modelA.doSomething(e.data) modelB.doSomethingElse(e.data) }); I'd take a simple approach like that over an event bus/dispatcher/coolest-new-pattern-since-sliced-bread - any day, and sleep at night knowing if something happens to me, any employee will be able to parse and debug my code. PS And no, I don't use Backbone, but my own 500 LOC MVC framework that's got zero dependencies and is about 100x faster.
- swah 12y agoBackbone is considered a high-quality javascript project, written by a very talented and experienced developer. Could you explain what you do differently in your MVC framework to achieve those gains?
- powatom 12y agoEasy - he doesn't support anybody else's requirements, app designs, or coding practices.
- powatom 12y agoIsn't this simply trading one level of complexity for another? Maybe the OP is just giving a bad example, or maybe I'm missing something - but one thing I really don't like in his example is that model B's 'waitFor' call needs to know about Model A, and that model A has an appDispatch. This kind of tightly coupled code isn't going to do anything useful for your codebase if you have a large application. It might make debugging easier right now because things are more explicit, but it will make you less flexible in the long term. It also doesn't solve the problem that you still need to understand that A has to finish before B can start - which I suppose most people would simply solve with an additional event. The OP does mention this in the article, but I'm not convinced that the trade off is worth it here. If I'm to explicitly state dependencies within the event handling code, then that means that any time I want to change how things handle events, I've got to remember exactly which of my dependents reference me. This isn't helpful - and in fact it may be more painful than the current situation. I guess my main problem here is that the benefits this brings just aren't enough (in my view) to offset the potential pain. To me, it kinda just feels like cutting off your nose to spite your face. You might make one area a bit easier to debug, but you lose out in other areas too. On the surface the code may look more sensible and easier to think about, but in practicality I'm not convinced that this won't introduce additional pain later on.