

Straightening our Backbone: A lesson in event-driven UI development - tdumitrescu
https://code.mixpanel.com/2015/04/08/straightening-our-backbone-a-lesson-in-event-driven-ui-development/

======
raspasov
This is a great example of "simple is better than easy". It always seems so
easy to "just make a callback" in JavaScript/UIs but when things go beyond a
very simple UI component things go downhill, FAST. This sort of callback
dependency nightmare is so common in workflows adopted by many JavaScript/UI
frameworks. It's scary and sad at the same time.

I encourage any JavaScript developer to seriously take a look at ClojureScript
+ React + core.async for front end development. Not a silver bullet, and
there's the learning curve of a new language, but definitely better tools to
build the foundation for an asynchronous & inherently complex UI. If the
problem/project you're solving/working on is not trivial, the payoffs are
there.

~~~
aikah
> It always seems so easy to "just make a callback"

DOM interactions are callback oriented. That's how the DOM is built. That's
why people write callbacks at first place. Why would you want people to work
differently? the api is what it is. You might think the api is broken, well,
it's not the front end developers fault, they didn't write the spec.

> but definitely better tools to build the foundation for an asynchronous &
> inherently complex UI.

React and CO are DOM abstractions. Which is fine if you know how the DOM works
at first place. The problem is most people don't bother learning how the damn
thing really works.

But at the end of the end of day, no front end developer can escape from
JavaScript or the DOM. When things break,you still have a big JavaScript stack
trace with DOM errors to debug,language A or B,framework X,Y,Z or not.

~~~
raspasov
I agree with you that there's no escaping the fundamental way the
DOM/JavaScript was setup in the first place - it's too late unfortunately,
that ship sailed in the 90s apparently :). I can write at least an article
about this stuff, but if you're interested in how core.async specifically
helps at least mitigate some of the callback spaghetti hell here's a good
starting video
[https://www.youtube.com/watch?v=AhxcGGeh5ho](https://www.youtube.com/watch?v=AhxcGGeh5ho)

------
guscost
Terrific write up. Recently I've been using the mantra "design transcends
(choice of) technology" and nowhere is it more apparent than with front-end
frameworks. Using React or whatever is hot will not make much of a difference
at scale if the application is not well designed. And conversely, it is
possible to design great apps with all sorts of alternatives to the brand du
jour. I'm still a big fan of the shiny new stuff, but kudos for applying new
and improved design concepts to your existing stack rather than jumping brands
under some delusion that it will solve all your problems. The community needs
more thinking like this.

~~~
pags
Definitely. I was surprised at how thoughtful and sane this article is.

------
joslin01
So, one flaw in the approach they displayed is the mediator is kicking off two
asynchronous processes. Yes, naturally race conditions will occur if process A
competes with process B. Furthermore, why is a mediator performing business
logic? The dispatcher / mediator / router / orchestrator seems to be presented
as an active object in of itself. It really shouldn't be.

Rather than manipulating or calling components from your mediator, you could
tell a coordinator that coordinates services. Services are then where you're
housing your business logic. This allows you to structure these service calls
and account for execution order.

>View initialization and render are separate steps

yes

> no events are fired during the app’s initial bootstrap/render process

sure

> on a given UI screen, a single top-level mediator takes on all
> responsibilities of communication/event-dispatching between subviews

> In practice, the code of the Orchestrator view becomes the centralized
> location and source of truth for all inter-widget communication in its
> purview

no?

There's a reason why actor systems are a thing. You can't just stuff
everything into a central object like it's a flat key/value store. It's better
(in my opinion) to form a hierarchy of responsibility. The "Orchestrator"
should hire some subordinates and delegate work to them in an effective
manner.

You guys must have an insane amount of business logic in your web app for any
given UI screen (which as I'm understanding it is different menu options I
guess? because otherwise a single UI screen is your entire app). The main
takeaway in what I'm saying is a router shouldn't be confused with a component
that executes business logic. It's doing too much.

~~~
tdumitrescu
> So, one flaw in the approach they displayed is the mediator is kicking off
> two asynchronous processes

hmm, seems like the diagram wasn't very clear on that point. there's only one
asynchronous action there: making a network request back to the server for
fresh data, which by its nature has to be an async op. everything else is
synchronous (including Backbone's event handlers).

> The "Orchestrator" should hire some subordinates and delegate work to them
> in an effective manner.

this is precisely what's happening: the orchestrator basically does nothing
but delegate between subviews and the model layer, ensuring that messages are
passed around as appropriate. the text probably didn't make clear enough that
this is supposed to happen at any sufficiently complex portion of the UI
("This pattern is repeatable: any sufficiently large or complex area of an app
(such as a single route) may hold its own mediator to encapsulate the details
of its UI interactions and communicate with the Model layer with a small,
well-defined API"). we don't really consider interactions between UI
components to be "business logic;" if the 'mediator' started turning into an
uber-object with many responsibilities we would probably look at breaking it
into services (or putting the right bits back into the model layer), but we
try pretty rigorously to avoid premature abstractions...

~~~
joslin01
ah, very interesting explanation thank you

------
shoover
This is a very good breakdown of a useful pattern. I think they dismiss
declarative approaches with too broad of strokes, but there is always room for
frank discussion of lighter, straightforward approaches that get the job done.
This overlaps with Codecademy's messaging architecture with React presented by
Bonnie Eisenman at React Conf [1], except Codecademy took it a step further
and dynamically generated the mediator linkage using mixins pertaining to the
views in play for a given screen.

We pretty much write desktop apps in this style but run into problems in some
complex situations, so I am parsing this for clues to where we may be letting
problems sneak in. The rule that programmatic UI updates never raise events
may be such a clue, indicating some views are doing too much direct
interaction with both local state and subviews.

Question: are there rules to apply to this architecture to help with data
modularity up and across the view tree? A lot of the work around React right
now has to do with creating techniques to avoid passing state across levels
that don't directly depend on it. For example, communicating an event two
levels up without involving the intervening parent or communicating across
subviews that don't share a common parent. As I read the OP, you have to pass
the event all the way up and the state all the way down, coupling the dataflow
and triggering rerenders pretty much all along the path.

[1]
[https://www.youtube.com/watch?v=ZM6wXoFTY3o&feature=youtu.be](https://www.youtube.com/watch?v=ZM6wXoFTY3o&feature=youtu.be)

------
denniskane
"Our Unix-inspired development philosophy favors the integration of
lightweight, independent apps and components instead of the monolithic mega-
app approach still common in web development."

I have essentially ported Unix itself onto the web at
[https://www.urdesk.net](https://www.urdesk.net) . You can say that the site
itself is a kind of "mega-app", but given that it is an entire OS-in-a-browser
that typically "installs" in < 2 seconds, I think it can be forgiven. There is
also an app that is a kind of "Hello World" for AI that uses the Speech
Recognition and Voice Synth capabilities of the Chrome browser. To hop
directly into the AI, you can check out
[https://www.urdesk.net/desk?intro=bertie](https://www.urdesk.net/desk?intro=bertie)

The site is now Chrome-only, since I am pretty much going all-in with the
whole AI theme, and none of the other vendors include the necessary Javascript
API's.

~~~
joslin01
I deleted root and it froze. Where can I file a bug report? This is
unacceptable.

~~~
denniskane
Do you mean: I typed: 'rm /' and the prompt never returned? This didn't
actually delete anything, you can always just do a ^C to get the prompt back
when it gets stuck like that. I wouldn't consider that a "bug", just an
unhandled error condition. More of a "glitch" that will take about 2 seconds
to work out. Thanks for the feedback!

EDIT: Already fixed with a nice friendly error message!

~~~
joslin01
Hey friend, I was just joking about "This is unacceptable." Sarcasm :) I do
`rm -r -- /`. Very cool project.

------
capkutay
Great write-up.

As far as backbone goes, we've had success using marionette.js to standardize
the way we write front-end components and implement common types of view
patterns. Curious to see if anyone else has found it to be useful.

~~~
zdavis1645
We've had the same experience. It adds just enough convention on top of
Backbone to prevent your application from turning into a ball of mud, while at
the same time remaining small enough where you can hold the whole framework
(Backbone + Marionette) in your head. I find Backbone + Marionette much
simpler, and much easier to reason about than Angular, but keeping it this way
still requires developer discipline. Avoid hacks, avoid event soup (which
Marionette maybe makes a bit too easy), and follow general object oriented
design patterns, and it's not that hard to build a maintainable, scalable
Backbone/Marionette app.

------
discreteevent
Good article but one thing to be careful of here is that if you just apply
this pattern everywhere then you can end up in a situation where every view is
decoupled from every other view and there are way more events in the system
than there need to be. This happens more frequently than you would think. In
the example given of a date picker this is fine as you can easily see that you
might want to re-use the date picker somewhere else. But there are often
circumstances where a subview could never be re-used outside of it's parent
view. For example suppose I have a main calendar view with subviews showing
appointments. The subviews need to inform the main view when the user changes
an appointment time so that the main view can re-layout all the subviews. In
this case there is no need for the main view to listen for events from
subviews. The code will be much simpler and easier to debug if the subview
just makes a call to the main view directly (the subview should be constructed
with a pointer to the main view). When I look at code in the subview I don't
have to guess who may be listening to the event, I can see the call directly.
It also means that other views apart from the main view cannot randomly listen
sub-view events as a 'quick fix' for some bug.

The point is that javascript tends to lead one down an event driven route. But
you should only use events if you want to decouple things. Cohesion is also
very important in design because it makes things simpler and more
encapsulated. You need to decide what the boundaries of your component are. In
this case I want the calendar to be the component, not it's subviews. So
within the calendar I will make direct function calls, there are less events
in the system and it is easier to think about and debug.

~~~
currywurst
But suppose you want to re-use the sub-view in another component, you would
then need to clone the functionality and satisfy/remove all the directly
coupled invocations.

This is where you have to decide how much coupling to introduce to balance
comprehension vs future needs of the project

~~~
tdumitrescu
indeed, it's a matter of juggling competing maintainability considerations and
there are cases where it can make sense to couple components tightly.

that said, in practice our UIs contain relatively few one-off components, and
it often requires less dev friction simply to use the standard event pattern
than to weigh borderline cases to shave off a little indirection at the cost
of tighter coupling. the problem of "too many events in the system" is avoided
by letting complicated components handle events from their own subviews and
not just blindly bounce everything down to a global mediator. e.g. if no other
views have to know about the 4 dropdowns and slider within MyWidget (and they
usually don't), then MyWidget can handle all those subview events itself and
simply present a single unified 'i've been updated' event for other consumers.
essentially narrow the public apis between different actors in the overall
system.

------
seri
Very often articles of this nature end up being either too abstract and too
vague or too detailed and I would lose focus midway. This is very finely
written.

------
twsted
Well written compendium of common design patterns.

I love Backbone for its simplicity but I think most problems come from the use
of the views' render function, which encourages the habit to have a
_centralized_ way to refresh theirs content.

This is, again, a simple way to think about the process, but often it is not
the most performant (and this is what has driven to the clever virtual DOM
diff-ing of the recent frameworks).

I normally use what I call 'micro-rendering' functions, a way to change the
appearance of a view focusing on the change of single state properties.

------
zupatol
Great post, thanks.

For some reason the css doesn't get applied to your page when I look at it in
firefox.

