The reactive-domain-objects library intelligently synchronizes any source of JSON data with rich observable Domain Models, providing a bridge from an Imperative programming model of stateful objects to a Reactive programming model of observables, data streams, and reactive user interfaces.
A primary use case is connecting GraphQL client libraries (such as ApolloGraphQL) to reactive client libraries (such as React) with Observables (such as MobX).
See below for an introduction to the library, and Full Technical/Usage Documentation for a detailed description of all the functionality, configuration options, and additional usage examples.
The problems being solved primarily revolve around consuming and using JSON data in TypeScript/JavaScript client applications:
-
The Encapsulation Problem: Most modern state management frameworks rely on plain JavaScript object trees. This makes encapsulation (of data, logic, and behavior) difficult which, in turn, makes managing complexity in large codebases or complex business domains much more challenging
-
The Denormalization Difficulty: When working directly with plain JavaScript trees of any non-trivial complexity, denormalization (and corresponding normalization) is usually required for efficient graph traversal. This adds additional complexity and maintenance requirements
-
External Data Sync: Many client UI frameworks, such as React, rely on referential equality of state data. Results returned from external data sources, including REST and GraphQL, will never be referentially equal, even if their structures and values are actually the same. Extensive memoization code, or poor performance result
- Automatic synchronization of Reactive Domain Object Graphs with an external data-source
- Automatic Change tracking, of external data-sources, with Reactive Domain Object Nodes only being updated only where changes are detected. When combined with Observable properties, this is a very powerful feature that reduces or eliminates the need for memoization code in dependent UI code
- Convention, Configuration, or Code options for specifying the construction of Reactive Domain Object Graphs
- Automatic Creation of Observable Domain Model Objects, (base on configuration settings)
- Emits Node Change Events, which can be subscribed to via the GraphSynchronizer object
- Rdo Synchronization Lifecycle Hooks, which can be implemented via interfaces
- Performance Optimized, with sub 0.3 millisecond synchronizations for a medium sized graph (as measured on a 2015 quad-core laptop. Performanc test available for local performance verification)
- One way data flow paradigm: Actions --> External Data Source --> State
- Custom Collection Types, to support easy graph traversal, and reduce the need for denormalization of data
// npm
npm i @ablestack/rdo, @ablestack/rdo-apollo-mobx-connector --save
// yarn
yarn add @ablestack/rdo, @ablestack/rdo-apollo-mobx-connector
// DEFINE REACTIVE DOMAIN GRAPH
class FooSimpleDomainGraph {
public bar = new BarRDO(); //*1
}
export class BarRDO {
public id: string = '';
public name: string = '';
}
// INSTANTIATE DOMAIN RDO AND GRAPH SYNCHRONIZER
const fooSimpleRDO = new FooDomainGraphSimple();
const graphSynchronizer = new GraphSynchronizer(/* Config Options Here */);
// SYNC
graphSynchronizer.smartSync({ rootRdo: fooSimpleRDO, rootSourceNode: { bar: { id: 'bar-1', name: 'Original Name' } } });
// Make any changes to the source data
// RESYNC
graphSynchronizer.smartSync({ rootRdo: fooSimpleRDO, rootSourceNode: { bar: { id: 'bar-1', name: 'New Name' } } });
- See Full Documentation for a detailed description of all the functionality, configuration options, and additional usage examples.
A Reactive Domain Object (RDO) is any object which satisfies the following conditions:
- Contains state, and optionally behavior and logic, relating a discrete entity in a program domain
- Internal state is automatically synchronized with changes in an external data-source
- Action methods are provided for all acceptable state mutations
- Follows a one-way data-flow paradigm, from: Action-Method --> External Data Source --> Internal-State (though synchronization)
- Observable Public Properties (optional but recommended)
- Child nodes exposed as Maps for efficient graph traversal (optional but recommended)
- Source & Target Structural Similarity. While field names can be adjusted, by configuration, the overall 'shape' and nesting structure of the graph must match between the source and target graphs. This library does not, yet, have the capability of automatically manipulating the shape of a graph during the synchronization process
- Array and Set collections types in RDOs are more processing intensive. It is suggested that they are avoided for collections that may contain a large number of elements (100+)
This code was initially developed for use in a single commercial project. It is being shared in case useful to others, and as a contribution to the development community and the great tools and libraries that already exist.
- Open to suggestions
- Find a clever way to provide a default mechanism to instantiate Domain Objects without the need for user configuration to provide these. Will probably require some kind of TypeScript reflection, as in 'empty domain collection' scenarios, an instance will not be available, and nor will type runtime type information
- Provide decorators that can be applied to the Domain Types to perform useful functions in lieu of configuration options, such as 'ignore'
This library is part of a collection of companion tools and libraries under the AbleStack umbrella. All of these libraries share the common goal:
Helping small teams and solo-developers build big ideas rapidly and affordably
To achieve these goals, the following principles are applied:
- Selectively leverage existing open source tools and libraries, where, high quality, open source tools and libraries where possible
- Curate usage examples, and guidance where available, and create where not available
- Prioritize technology choices that embrace open source
- PostgreSQL over MSSQL is an example of this
- Avoid technology choices that could result in hosting vendor lock-in
- ApolloGraphQL over AWS Amplify is an example of this
- Automate wherever possible, from development, through testing, to deployment, monitoring, and maintenance
- Codegen from strongly types schemas is a good example of this.
- Where needed, develop high quality, open source tools and libraries to augment and automate existing open source tooling and libraries
While this library solves several challenges relating to client-side JavaScript development, it was developed to solve some very specific use cases, and might not be the right solution for everyone. It is definitely worth checkout out these alternative solutions before making your choice:
-
MobX-state-tree is similar in many respects to this library. reactive-domain-objects is designed to solve similar problems, but is less opinionated, less complicated, but also less sophisticated. For more advanced features and a more opinionated solution, MobX-state-tree is definitely worth checking out
-
Vanilla MobX. Many of the benefits reactive-domain-objects come from the integration with the great MobX library. For small and even mid-sized solutions, the same result can be achieved with MobX alone. The primary added benefit that reactive-domain-objects provides is automating the synchronization of data with external data sources. The point at which that task becomes more complex or tiresome than adding another dependency, or learning another library, is the right point at which to get reactive-domain-objects in the mix
- Initial release
- Performance updates
- Refactorings for improved maintainability
- Added auto-Rdo creation functionality
- Added GraphSynchronizer EventEmitter. This broadcast all node change event to any subscribers