Adopt ImmutableJS as a core design pattern #14

Closed
kitsonk opened this Issue Jan 20, 2016 · 11 comments

Projects

None yet

8 participants

@kitsonk
Member
kitsonk commented Jan 20, 2016

Topic

Should we adopt ImmutableJS as a core design pattern for Dojo 2?

Background

ImmutableJS provides a library for dealing with immutable data.

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

Immutable provides Persistent Immutable List, Stack, Map, OrderedMap, Set, OrderedSet and Record. They are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data.

Immutable also provides a lazy Seq, allowing efficient chaining of collection methods like map and filter without creating intermediate representations. Create some Seq with Range and Repeat.

Considerations

  • How would we integrate ImmutableJS?
  • How would it integrate to our build pipeline?
  • What APIs should we change or replace or augment to leverage ImmutableJS and how do we expose it to the end developer?
@kitsonk kitsonk added the discussion label Jan 20, 2016
@morrinene morrinene added this to the Milestone 1 milestone Jan 21, 2016
@vansimke vansimke was assigned by morrinene Mar 1, 2016
@vansimke
vansimke commented Mar 1, 2016

Part of the challenge with an immutable system is that it isn't possible to realize it in a pure manner. Eventually, any non-trivial application must mutate some sort of state in order to be useful (consider the need to mutate the state of the video buffer that is required to render content to the screen). In light of that, the question is not about whether an application can be purely immutable, but rather how to determine which aspects should be mutable or not.

Gary Bernhardt have an interesting talk called "boundaries" where he poses the idea of an architecture that is composed of immutable, functional cores that contain state and business logic surrounded by imperative shells that interact with the mutable parts of the environment. He builds a compelling argument that such as system promises to be easier to test and maintain since it separates the areas with a lot of execution paths into the functional core from aspects of the application that require a large number of dependencies, which are pushed into the imperative shell.

Following this principle could give us an answer to the first question posed. We would integrate ImmutableJS into a functional core of the architecture that is not allowed to mutate. This would be where we would store the state and logic of each sub-system. These sub-systems would be wrapped by mutable layers that would allow them to interact with one another in a stateless manner (via message passing). These mutable layers would also be responsible for responding the the changing environment that the application lives in (e.g. events being fired, text being entered, server data being received) and would respond to these mutations be requesting new states from the functional core which would then be used to create the new shape of the application in response to the mutation.

As an example, let's take routing in a single page application. At its core, the Dojo1 router almost implements this system already. In general RouterBase only mutates its state when new routes are registered, which could easily be converted to adopt an immutable model. The only other part of the router that does not align with this architecture lies in the startup() method. It registers a listener for the 'dojo/hashchange' topic that is triggered in response to an external mutation (the change of the browser hash). It would be a trivial thing to extract this into a "Navigator" class that would exist in the imperative shell and, when trigger, create a new router based on the new hash-state in the application. The router could still hold all of the registered callbacks and call them as required or, since they would likely cause mutations in other sub-systems, could be returned via a filtering operation to the imperative shell which could execute them.

The advantage of this separation is that the router would become trivial to test since all it would be doing would be creating copies of itself or filtering registered route handlers based on its own state. Similarly, the navigator would be equally trivial since it would only listen for topics to be published and, when they were, it would create a new router, ask it for the correct callbacks, and then call them.

@kitsonk
Member
kitsonk commented Mar 2, 2016

I think we should clarify something. ImmutableJS isn't an application framework. It a toolset, which would be used appropriately in an application framework. It is one way of actually dealing with change in an application. Instead of having to "watch" things for change, you enforce you can't change them and you simply discard them when you need to replace them.

Adopting it would mean that we would have tools to leverage those appropriately and make a statement that we generally prefer immutability and object diffing instead of dirty polling to deal with changes.

@kitsonk kitsonk modified the milestone: alpha.1, Milestone 1 Mar 11, 2016
@kitsonk kitsonk modified the milestone: 2016.04, alpha.1 Apr 8, 2016
@mwistrand

Based on what I have heard elsewhere, I cannot tell whether this decision has already been made, but I will offer my two cents anyway.

After having been bitten hard in the past by changing state, I really like Immutable.js. Using an immutable data architecture would help make Dojo 2 apps easier to follow, and would perhaps even mitigate the need for complex diffing in a virtual DOM. Further, looking at the architecture proposed for widgets, an immutable domain model makes a lot of sense. The main arguments I can think of against using Immutable.js are:

  1. There might be performance concerns when creating new objects for every change.
  2. Since Immutable.js objects use a lot of the same methods as native JS types, it can be hard to figure out whether our functions are meant to consume native JS types or Immutable.js objects.
  3. Getting this to work with technologies like dstore and dgrid is not a simple task.

Concerns #1 and #2 are admittedly straw man arguments to an extent, considering that Immutable.js is smart enough to minimize changes to the returned object, and since TypeScript itself makes it easy to know whether we are dealing with plain JS types or Immutable.js objects. #3 definitely is a concern if dstore and dgrid in their current implementations are to have a place in the Dojo 2 architecture. That said, it sounds as if dgrid is being modified for compatibility with Dojo 2, and there is a new (albeit empty) dojo/stores repo, so that may not be a problem.

@kitsonk
Member
kitsonk commented Apr 12, 2016

@mwistrand I don't think the decision has been made. Right now, it is in the widgets proposal, but could be reversed. It is a fair amount of overhead to "enforce" something that in ways the API that surround it should support anyways.

It is also fairly chunky, even in its minified state at runtime. It can also thrash GC quite a lot (because of your point 1). I have gone back and forth on it in my prototyping widgets (which is why, when we get further with stores, I think it will be informative as to its long term value). It certainly does remove the "foot gun" that I think comes with not having to worry about if someone has a reference to something and changes it in a way you weren't aware of.

There are some ways of dealing with large mutation operations in Immutable.JS too that reduces the number of transitory "dead" objects you create, therefore reducing the impact of your point 1.

@kitsonk kitsonk modified the milestone: 2016.05, 2016.04 May 3, 2016
@kitsonk kitsonk modified the milestone: 2016.06, 2016.05 Jun 7, 2016
@kitsonk kitsonk modified the milestone: 2016.06, 2016.07 Jul 4, 2016
@kitsonk kitsonk modified the milestone: 2016.07, 2016.08 Aug 1, 2016
@kitsonk
Member
kitsonk commented Sep 15, 2016

I revisited this when dealing with the implementation in dojo/widget, and while we tend to manage things like they are Immutable, which raised concerns in my mind if we would use it with a level of overhead for what was a limited benefit of enforcing immutability.

In looking at it though, not only does ImmutableJS provide the enforced immutability, it also provides a more uniform and feature rich API... For example List and Map provide .equals() which makes it easy to deep compare instances of these objects, where as Array and Map (the native equivalents) don't have this functional API and would require extensive branched logic to provide the same functionality.

@kitsonk
Member
kitsonk commented Sep 15, 2016
@kitsonk
Member
kitsonk commented Oct 4, 2016

For now, we will continue to use ImmutableJS where appropriate. It maybe worth revisiting providing a more consistent API in dojo-core long term if we find we are really challenged with the overhead of ImmutableJS.

@kitsonk kitsonk closed this Oct 4, 2016
@kitsonk
Member
kitsonk commented Oct 24, 2016

@matt-gadd has looked at building applications and our basic application, 22% of the code base is ImmutableJS:

image

So, let's re-open this issue and discuss further, because that brings it into question again, its usefulness to us.

@kitsonk kitsonk reopened this Oct 24, 2016
@kitsonk
Member
kitsonk commented Oct 24, 2016

Before, in parent widgets, we had two types, a Map and a List. We have now unified those interfaces and replaced it with OrderedMap. So for widgets, the only critical change to get rid of the dependency is find a way to provide a functional replacement for OrderedMap.

@kitsonk
Member
kitsonk commented Oct 24, 2016

dojo/core#217 and dojo/core#218 propose adding List and OrderedMap which would then likely provide us with enough functionality to not require ImmutableJS for widgets.

@dylans dylans modified the milestone: 2016.10, 2016.08 Oct 24, 2016
@kitsonk
Member
kitsonk commented Oct 26, 2016

The widget interfaces have been migrated to ES Map for now. The additional functionality can be addressed at some point in the future supported by use cases. So again, closing.

@kitsonk kitsonk closed this Oct 26, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment