If you’re a software architect and not a theoretical physicist, you don’t have to be as insightful as Einstein. But it helps!
The Principle of Locality
When Albert Einstein published his Special Theory of Relativity, he knew it was only a temporary hack. The theory was accurate and powerful, but it violated an important principle – the principle of locality.
The principle of locality says that you can never have action at a distance - you can only make things happen by starting chains of local effects. If I want to say “hi” to you, I can’t vibrate your eardrum from a distance. I can only vibrate the air near my mouth, and start a chain reaction that eventually reaches your ear.
Einstein was sure that all physical laws were local in nature. He wrote:
Of objects far apart in space, A and B: external influence on A has no direct influence on B; this is known as the Principle of Local Action, which is used consistently only in field theory. If this axiom were to be completely abolished, the idea of the existence of quasienclosed systems, and thereby the postulation of laws which can be checked empirically in the accepted sense, would become impossible. – Albert Einstein, ”Quantum Mechanics and Reality” (“Quanten-Mechanik und Wirklichkeit”, Dialectica 2:320-324, 1948)
We know sound vibrations obey the principle of locality, but what about gravity? Don’t gravitational forces act at a distance? That’s what Isaac Newton thought in 1686, when he invented the original concept of gravity. Newton thought the Earth, as it moves around the sun, instantly changes the direction of its gravitational pull on the moon. (Newton believed that if you want to send a message faster than light, you can theoretically just hop around in a Morse code pattern, and someone far away from you can receive your message instantaneously by measuring the disturbance in the gravitational force field at their own location.)
Einstein wasn’t happy with the non-locality of Newton’s theory of gravity. But when he published his Special Theory of Relativity in 1905, he still hadn’t managed to give a local account of gravity. Similarly, a lot of programmers talk a good game about the virtues of modularity, componentization and “local” variables. There’s even a Wikipedia article called “Action at a distance (computer programming)“. And yet, it seems like modern software architectures violate locality a lot.
Locality in Software Architecture
jQuery is Non-Local
Say you make a UI component for a restaurant review. You design the component so it can take just two input parameters – a data object of type
Restaurant and a data object of type
Review - and render something like this:
It’s nice to think of your components as self-sufficient modules that only interface with the rest of your app by passing a few parameter values. But if your app’s architecture doesn’t obey the principle of locality, you won’t be able to rely on this convenient property.
A typical non-local thing people do in web apps is use globally-scoped jQuery selectors. For example, if the app learns that John C. just changed his user icon, it might execute an instruction like
$(".user_icon").attr("src", john.iconUrl) from somewhere outside the review component. This is certainly one way to dynamically update John’s icon images everywhere they appear on the page. But since this approach bypasses the component’s parameter interface, it’s action at a distance.
It’s as if the physical universe had let me talk to you by directly vibrating your eardrum. It’s a quick and dirty way to get my message to you, but since the sound doesn’t propagate in a local way, the earmuffs you just bought to block the sound can’t function. By violating locality in the case of user-icon-propagation, I’ve undermined the modularity of my whole component architecture.
So how do I write locality-preserving code for updating John C.’s user icon everywhere it appears on your web page? The trick is to propagate the
iconUrl update through every component’s explicitly-defined interface, instead of voodoo-jQuerying your way in. One way to do that is to think of your review-displayer component as having an “input wire” for a user object, in this case
john. Then when you want to update the displayed icon, you just have to alter the signal on the
ReviewComponent.user wire. And if you use a model-view framework like Backbone, you can just make
john an instance of your user model class, and connect it to your
AJAX is Non-Local
You’ve followed in Einstein’s footsteps, and now you have a
ReviewComponent whose behavior is fully determined by its local surroundings (the objects connected to its “input wires”). But what if you want your
ReviewComponent to have dynamic AJAX functionality?
Say you want to build another feature for your
ReviewComponent: a live, up-to-the-minute reading of the user’s rank on the site (as determined by some secret formula combining number of reviews written and upvotes from other users). The naive approach for loading the data is to call something like
$.ajax('/getUserRank') in your
ReviewComponent implementation. Seems like a reasonable solution, right? Too bad it completely destroys the property of locality.
You started with a
ReviewComponent which state and behavior was entirely determined by short wires poking into its local environment. Now your
ReviewComponent has a giant wire that travels all the way out of the local environment and into the browser’s AJAX API. (It’s a shame people don’t use programming IDEs that visualize the component dependency graph, because it’s all too easy to write an innocent-seeming line of code that introduces a huge wire.)
Ok, so you violated locality and accessed your AJAX API directly from inside a UI component… what’s the big deal? It might seem like your component architecture can withstand these kinds of locality violations. But if you put on your Einstein hat and aim for true elegance – true archutectural modularity and component reusability – you might find that your code is more fragile than you think when it comes to these kinds of locality violations. Here’s the thing about AJAX calls inside components:
You broke the invariant that a component’s state is a function of the data on its input wires. Now you don’t have an elegant representation of your component’s state.
The fix for this is to have the component’s AJAX response handler put the results of the call on an input wire.
You hard-coded the global address of the AJAX call handler. Now it’s not really a component, because it’s tethered to your server.
The fix for this is to leave method-addressing semantics up to the control’s local environment. Instead of sending the
getUserRank method call directly to your faraway, globally-addressed server, you can ask your local environment to
getUserRank for you, whatever that may mean in context. Of course, the most obvious thing for the component’s environment to do is to route your
getUserRank call to the nearest server, and you get the same effect as
$.ajax. But if you place your component in different environments, you can imagine that some of them will route your method call to a different server, or even answer your method call using client-side logic. If you realize that AJAX calls are really method calls, and hard-coded AJAX violates locality, you’re one step closer to the holy grail of modularity – truly reusable UI components.
You baked a specific data-retrieval mechanism into your UI code. Now you can’t take advantage of frameworks that help with things like polling, caching and WebSockets.
Your real goal isn’t to “do an AJAX call”. Your real goal is to wire your view component’s UI to the output of a (parameterized) method named
getUserRank. The fact that you’re using an AJAX call to jump the gap between your client-side and server-side app should be completely irrelevant to your component. First, the client-server gap isn’t located “adjacent” to your component, so it isn’t your component’s business (the previous section addresses this point further). And second, the fact that you’re using AJAX as your data-retrieval mechanism should also be irrelevant to your component.
A modular component is a unit of UI-rendering logic specified as a function of locally-present data. A modular component never has to talk about data-retrieval concepts like AJAX, polling, caching, websockets, JSON serialization, etc. The
ReviewComponent in our example should say “I want to propagate the abstract output of
getUserRank into this part of my UI like so”. It should be up to the external environment to take care that the abstract output of
getUserRank is present on the component’s input wires. Locality is the key to factoring data retrieval logic out of UI rendering logic.
For this reason, it’s a shame to ever write
$.ajax in a component’s code. Unfortunately, we don’t know of any programming frameworks with idiomatic ways to write fully local components. The Functional Reactive Programming model, as seen in frameworks like Flapjax, Elm and Asana’s Luna, looks like a step in the right direction for true componentization. For now, you might have to bite the bullet and keep writing AJAX calls that violate locality.
The principle of locality points the way forward in software architecture as surely as in physics. Einstein could have just taken a break after formulating his Special Theory, but he wasn’t happy with a still-non-local account of gravitation. Einstein’s obsessive dedication to the principle of locality led to his most powerful and elegant theory ten years later – the General Theory of Relativity.
If you design your app’s architecture with locality in mind, you probably won’t discover one of the most profound insights in the entire 400-year history of science. But you’ll write amazingly elegant and modular code. Whether you’re unlocking the secrets of the universe or programming an app, it pays to think about locality.