Fighting Complexity

Thomas Jung
Mindful Technology
Published in
4 min readOct 1, 2017

All we wanted for our Meteor app was to know when the URL in the browser changed so we could update our app controller’s top-level state. The ultimate goal was to reign in on the accidental complexity–and resulting fragility–inherent in having application components query or depend on any form of global state–in this case the browser’s URL–directly. This post explores two aspects of the problem:

  1. Too many layered abstractions: How to get from a browser’s URL to a “renderable state.”
  2. Not enough compositional abstraction: How to separate the responsibility for state control and data flow.

Disclaimer: This post is not meant to diss on Flow Router. The default tools and patterns provided by the Meteor community are a great foundation for quickly building reasonably complex web applications. I simply wanted to document our research.

Accidental Complexity

Typically, most modern web applications, SPAs, rely on a router to determine how to update the UI based on the current path. Such routers are specialized controllers abstracting only a subset of the entire application state. As applications grow, this abstraction not only turns out to be leaky, it also often results in tight coupling between components’ states and the current URL. Add to this the concept of reactivity as used in Meteor applications and you quickly find yourself spending a significant amount of time in a deep rabbit hole debugging weird UI state and behavior. Complex applications lend themselves to a layered “controller” approach with increasing top-down specialization anyway, so my goal was to convert route changes into a state tailored to our application’s top-level state manager (a controller).

Magic Not Wanted

In previous applications, we fought with the reactive state of IronRouter and were looking for something much simpler that gave us greater control. The “less reactive” Flow Router has become the de-facto standard in the Meteor community. One of the patterns they promote is to push subscription and authentication out of the router. I can appreciated that concept, but it’s not going far enough: Delegating it directly to templates is, IMHO, just moving the complexity elsewhere rather than simplifying it.

Do One Thing And Do It Well

To me, the proposed patterns violate the Single Responsibility Principle, arguably one of the two most important principles to keep software maintainable. Most (all?) templates should not be responsible for getting their own data and most top-level template modules should not be configuring their own routes, route groups, or listen to state changes. Those best practices are well known to the React community and mostly enforced by how React works in the first place. There is no reason, however, that these same principles cannot be applied in an application not using React.

There are several aspects and requirements in a complex application that should be separated into increasing top-down specializations instead of using cross-cutting concerns. Authentication and authorization ought to be the responsibility of a single component of the application, which will likely be a composition of several specialized, properly unit-tested sub-components. At the time a given template is rendered, it should not concern itself with whether the current user can see its content or not. Those restrictions should be explicitly configurable elsewhere, which also helps with reusability of UI components in different contexts.

To be clear, I don’t think for a moment that Flow/Iron Router & Co. cause poorly structured code. They do not, however, prevent it and their documentation seems to promote it. I certainly made several mistakes in structuring previous applications’ code because I relied too much on the examples and not enough on analyzing the data flow through our application. That one is on me!

Under The Hood

I did not want to repeat that mistake, so as part of our transition from IronRouter to something else, I started poking around in the Flow Router code.

Putting together a little prototype of how I wanted state to propagate, I discovered that now I needed two dependencies for my application: the router and blaze-layout. While reading the source code of both, I also found that Flow Router itself depends on a doubly-patched version of page.js. One patch is in a custom fork of page.js to fix an IE problem with a leading path slash for which the original ticket has had a pending pull request since July 2015. The other patch is done inline to address double-encoding with + conversion. All I really needed is the current path and when it changed so that the app controller knows which top-level template to Blaze.render. We ended up putting that responsibility into a single, relatively simple Router component:

I really appreciate the hard work of all involved contributors, but to me there are just too many layers of code–sometimes fixing accidental complexity in the layer below–only to end up with a different solution than we need to a problem we don’t really have. Flow Router supports route change triggers on top of a router that looks a bit like express.js. Blaze Layout also supports regions. None of these abstractions are useful in our application as such. All of this abstraction code has to be downloaded to every browser for our app’s users, which takes a (small) bit of time.

To conclude, I want to reiterate that our challenges were not the direct fault of any of these tools and dependencies: I only discovered the functional mismatch because I finally bit the bullet and analyzed this all the way through, which I should have done earlier on previous applications. The result of that effort was several thousand lines fewer of dependencies’ code in our application.

--

--

Thomas Jung
Mindful Technology

Building stuff made of bits and bytes. Cofounder and CTO of DealPoint.io