Workday Prism Analytics: The Search for a Strongly-Typed, Immutable State
By Michael Habib, Software Development Engineer, Workday
As does any application in today’s ever-changing front-end landscape, the UI framework of Workday Prism Analytics has undergone a series of transformations as it has evolved. Today, our Workday Prism Analytics front-end is set to undergo another exciting shift towards a more modular architecture.
In regards to a view model, we wanted a single source of truth for state transparency and update clarity, so Redux seemed like a fitting state-management pattern. Here, components dispatch actions — objects composed of a type and payload — that are processed by pure functions known as reducers. The reducers in turn update the store, or state, of the application. We then use Reselect selectors to read necessary properties from our store in a modular fashion, hiding all state access from the components themselves.
With this approach, there is unfortunately no way, other than developer due diligence, to guarantee absolute immutability. Accidental state mutations can slip through reviews undetected and cause complications. Complex linters to detect state assignment can help ameliorate some woes here, but none are perfect. Finally, with complex state, nested updates likely will result in multiple levels of copying and can quickly escalate into slow and unmaintainable operations.
At first glance, Immutable seems like an extremely promising option, but can it conform to a strongly-typed language? Let’s dive a little deeper.
First, Immutable adds a layer of abstraction with its custom structures, meaning developers need to learn a new, slightly verbose API for setting and getting properties. Additionally, since we wish to create reusable components that other teams can consume, we need to avoid introducing external dependencies in the components themselves. Therefore, Immutable must be restricted to our reducers and selectors.
Immutable.js with Records
We can ameliorate several of these issues by using another Map-like object introduced by Immutable: Record. With a bit of overhead, we can implement custom Records with explicit types represented in our state, allowing us to avoid using fromJS in state initialization.
Now, our state not only has all the benefits Immutable offers, but it is also strongly typed, so methods such as set, get, and merge can realize TypeScript’s static type-checking.
Unfortunately, we still run into issues here with nested structures; neither setIn nor getIn, used to update and get nested properties, preserves types. Using either of these extremely useful methods again sacrifices our beneficial type-checking, and implementing a combination of get, set, and mergeDeep, if possible, can quickly become unmaintainable. Furthermore, Records do not solve our problem with toJS’s performance when mapping state properties to components in our selectors.
This library has some detrimental flaws, however, that prevent us from seriously considering it for Workday Prism Analytics’ use case. First, when trying to update or read nested state properties, Seamless runs into the same problem of type-loss that we saw with Immutable Records. Second, as of this writing, Microsoft Internet Explorer 9 (IE) and later doesn’t support this library in its entirety. Workday supports IE 9 and later, and maintaining a separate immutability framework solely for IE seems extremely impractical.
Exploring another option, Immer is a lightweight library written by Michel Weststrate (creator of the Redux alternative MobX), which works by using proxies and copy-on-write. Given an initial state, it produces a proxied draft that we can freely modify and ultimately returns a new object, leaving our initial state unchanged. As a result, Immer guarantees immutability. Additionally, due to its use of structural sharing and proxies, Immer processes updates as performantly as Immutable.
This library comes with its drawbacks as well. IE11 does not support proxies, meaning Immer needs to rely on its significantly slower es5 fallback, a potentially significant issue for complex state updates. Fortunately, Immer’s simplicity makes it completely opt-in on a reducer by reducer basis, so native objects — or another alternative — could be selectively used if need be.
Second, Immer is still in its infancy. Michel Weststrate published Immer roughly three months ago (as of this writing), and it does not have the backing support of a large company like Immutable does via Facebook. Issues, should they arise, have the potential of going unaddressed.
We explored several options here on our search for a strongly-typed, immutable application store, but these are not by any means the only ones available. In fact, Redux Ecosystems alone lists over 65 packages to help with immutable data structures. Of these, immutability-helper and immutable-assign also stood out as other viable options, the latter seeming similar to, albeit lesser known than, Immer.
Given this vast multitude of frameworks and helper packages, it would be impossible to examine each one in detail. We considered certain libraries because of their prevalence in the front-end community, simplicity, reliability, and potential for TypeScript compatibility. Other frameworks may better suit different needs, especially if there is no desired support for TypeScript or IE.
In the end, we chose Immer for Workday Prism Analytics’ immutability use case. We set out at the start to find a framework, or lack thereof, that guaranteed an immutable store, while integrating seamlessly with TypeScript’s strongly-typed syntax. Despite its downsides, Immer not only fulfills both of these requirements, but is also lightweight, simple, and generally performant. Thus far, developers enjoy using Immer; it has been extremely non-intrusive and easy to uptake with little-to-no learning curve.
As we’ve seen, strongly-typed immutability has quite a few possible solutions, each with its own benefits and downsides. With the prevalence of so many intersecting technologies, hopefully this article helps to simplify your choices in building a modern, scalable front-end application.