As a developer today, when I stand back and survey what’s happened since I started writing code in the 1970s, what I notice more than anything else is volume. There’s a lot more code out there competing for attention than there used to be.
I’m glad that software has been commoditized; it makes me feel that I made a good choice to stick with it in my youth. Even though writing code is one of the hardest pure intellectual pursuits that exist, it’s also satisfying to the makers of the world, the folks who build things that do what they’re supposed to do. I can understand why so many others are now in the mix. Programming is a rush.
Because programming is so much fun for so many people, what used to be a fairly contained discipline reached the tipping point and became chaotic shortly after the advent of personal computers and when new generation of system developers appeared. They realized that building systems on low-cost machinery was the new way. The barrier for entry to become a programmer was lowered so dramatically that the floodgates opened and instead of there being just a few established ways to solve problems, there are now hundreds.
When Meteor appeared several years ago, it mainstreamed Reactivity — the idea that most of the logic embodied in applications could be written declaratively rather than imperatively. In Meteor, an application changes it’s presentation directly based on reacting to state changes. The notion of a Reactive Source was established, in which computations are re-computed whenever the value held by that source changes. While it doesn’t seem like much on the surface, it fundamentally altered the way many developers, including myself, write web applications.
Of course, reactivity is not the only game in town. Because imperative programming came first (reductionist algorithms are easy to grasp than holistic systems) there is much more non-reactive code available than reactive. Some of this code is really great and we should use it — because there’s no time these days to re-invent the wheel. But because it’s not reactive, it doesn’t in the reactive paradigm. At least, not out of the box.
How can the reactivity-focused developer use imperatively-activated code?
The solution is to make the reactive code appropriately drive the life-cycle of the imperative code.
Okay, great. That seems logical. Except to create the concept of a driver, we’re talking about a declarative computation acting imparatively. How can we make this happen?
The short answer is we need a mechanism. The long answer becomes clear when it’s understood how a system makes declarative programming work. While reactivity may be a revelation and a joy, it is effectively a fiction. A lot is going on behind the curtain to evoke the magical feel of declarative programming.
Here, we’ll say that the activeUsers collection (a reactive data source) keeps track of the last sign on date of a user to the system. The function passed to Tracker.autorun is a reactive computation that will count the number of unique active users that have signed on since midnight. This value is then stored into a reactive variable that other computations can use. What happens mechanically is when the activeUsers collection changes (it’s reactive) the computation is invalidated and it is scheduled to be recomputed. This triggering in Meteor is what gives it its reactivity.
I have seen complex layerings of reactive computations in reactive code that just magically seem to work. The secret is that the complexity is an illusion; it emerges out of many simple computations that are interrelated using reactive dependencies that change as recomputed values cascade within the system. Meteor’s reactivity is a wonderful vehicle for building systems with emergent behavior. But I digress.
What we need to do is use Meteor’s reactivity to latch a reactive computation.
When the code starts, a reactive variable is created to be used as a latch and is set to false, the unlatched state.
Following this, a function is passed to the dependency tracker to be re-run when any of the reactive sources it depends upon change. Since it depends on the state of the latch it will be run whenever the latch is set to true, the latched state. If latched, a function will be called to start the ball rolling on the imperative object (though we indicate we’re kicking off a life cycle event, what we do depends on the object being controlled — more on this later.) Once the manipulation of the imperative object is done, the latch is unlatched to keep from triggering the process again.
Finally, two additional functions,
thing2, are created to set some internal state by doing some sort of work, and then latching the latch to indicate that everything’s ready for the imperative object to react to.
This may seem like the long way around, but the imperative object may depend on state changes that can only be provided by doing some coordinated work ahead of kicking off a life cycle event.
Sometimes latching just isn’t enough.
someOtherWork calls made by
thing2 do things that may schedule work for meteor to be processed at the end of the re-compute cycle via invalidation, a potential race condition may occur.
When we want things to happen in a defined order, the latch could be modified to become a more comprehensive state rather than a simple on-off switch.
But when a state machine feels like overkill, kicking off the life cycle event on the imperative object inside of a
Meteor.defer may work well enough.
This basically indicates that the function passed should be executed once all the other pending work to recompute the invalidated reactive computations has completed.
If what your doing doesn’t have tendrils that reach out and reactively affect lots of other things in the Meteor world, you probably don’t need the deferment. But it’s nice to know it’s there if you do.
Imperative Object Life Cycle Events
There are generally two types of events that need to be kicked off to the imperative object through a reactive latch: an update event, or a restart event.
An update event generally says “something that you built up needs to have this added to it, or deleted from it, or changed within it.” The idea is that the things that need to be reactively constructed to hand to the imperative object aren’t ready until the life cycle event is kicked off. Only at that point can the update happen. Interestingly, the add/remove/change protocol matches the underlying detail available through Meteor’s DDP (distributed data protocol) system that provides reactive data life-cycle events to a Meteor application.
On the other hand, a restart event is more like “delete everything you already’ve done and redo it with this instead.” Here, it’s expected that a simple update isn’t enough (or such an update capability may not exist) so the only way to do what must be done is a complete restart. When restarting is lightweight, this is generally fine; if it’s heavier and requires some extensive work, you may want to rethink using such a component altogether — or stop using it in a live-data, Meteor way and instead use it only on-demand.
Reactive latching is a good vehicle for bridging the imperative component to the declarative ecosystem. It may take a little finessing, but if there’s a component that does everything you need it to do except function reactively, latching can get you up and running quickly and cleanly.