Ambitious Data Flows with Ember.js and Orbit.js

Ambitious Data Flows with Ember.js and Orbit.js

Introduction to Orbit.js and Ember-Orbit, including some development patterns for working with client-side data. Presented at EmberFest 2014 in Barcelona.

E01ec1de2f7783812d2235a6a9aaaeea?s=128

Dan Gebhardt

August 28, 2014
Tweet

Transcript

  1. Dan Gebhardt @dgeb AMBITIOUS DATA FLOWS WITH EMBER.JS AND ORBIT.JS

  2. None
  3. A standalone library for coordinating access to data sources and

    keeping their contents synchronized.
  4. None
  5. OFFLINE SUPPORT

  6. NON-BLOCKING INTERFACES

  7. DATA SYNCHRONIZATION

  8. TRANSACTIONAL CONTEXTS

  9. UNDO / REDO

  10. CORE CONCEPTS

  11. None
  12. None
  13. COMMON INTERFACES

  14. NORMALIZED DATA

  15. EVENTED CONNECTIONS

  16. PROMISIFIED METHODS doThing

  17. CORE CONCEPTS { } COMMON INTERFACES NORMALIZED DATA EVENTED CONNECTIONS

    PROMISIFIED METHODS !
  18. INTERNALS

  19. PLAIN OLE’ ==

  20. PRIMITIVES { } EVENTS

  21. PRIMITIVES { } EVENTS

  22. PRIMITIVES { } EVENTS TRANSFORMS

  23. SYNCHRONOUS EVENT HANDLING 1 2 3 4 5 6

  24. 1 2 3 4 5 6 7 ??? P P

    = Promise SYNCHRONOUS EVENT HANDLING
  25. PROMISE-AWARE EVENTS 1 2 3 4 5 6 7 8

    P P P P P = Promise Async Blocking
  26. PROMISE-AWARE EVENTS 1 2 3 ??? 4 5 6 7

    P = Promise P P Async Non-Blocking
  27. PRIMARY INTERFACES { } REQUESTABLE TRANSFORMABLE

  28. REQUESTABLE find add remove update patch findLink addLink removeLink findLinked

  29. TRANSFORMABLE transform(operation)

  30. REQUEST FLOW << assistFind >>! _find()! << rescueFind >>! <<

    didFind /didNotFind >> source.find(“planet”, “1”)
  31. REQUEST FLOW << assistFind >>! _find()! << rescueFind >>! <<

    didFind /didNotFind >> source.find(“planet”, “1”)
  32. REQUEST FLOW << assistFind >>! _find()! << rescueFind >>! <<

    didFind /didNotFind >> source.find(“planet”, “1”)
  33. REQUEST FLOW source.find(“planet”, “1”) << assistFind >>! _find()! << rescueFind

    >>! << didFind /didNotFind >>
  34. TRANSFORM FLOW source.transform(op) _transform()! << didTransform >>

  35. TRANSFORM FLOW source.transform(op) _transform()! << didTransform >>

  36. Orbit.Document Complete implementation of JSON Patch (RFC 6902): { }

    add remove replace move copy test ———— transform retrieve
  37. TRANSFORMATIONS JSON Patch transformations: {"op":"remove","path":["planet","1389295329110.3"]}! {"op":"add","path":["planet","1389295895882.1"],"value": {"name":"Mercury","id":"1389295895882.1"}}! {"op":"remove","path":["planet","1389295895882.1"]}! {"op":"add","path":["planet","1389296188090.2"],"value": {"name":"Mercury","id":"1389296188090.2"}}!

    {"op":"add","path":["planet","1389296196274.3"],"value": {"name":"Venus","id":"1389296196274.3"}}! {"op":"add","path":["planet","1389296197897.4"],"value": {"name":"Earth","id":"1389296197897.4"}}! {"op":"add","path":["planet","1389296199041.5"],"value": {"name":"Mars","id":"1389296199041.5"}}! !
  38. CONNECTORS • TransformConnector for connecting a source and target. •

    Two connectors are needed for bi-directional syncs. • Connectors can be blocking or not • RequestConnector for coordinating requests to data
  39. COMMON LIBRARY

  40. ORBIT COMMON LIB 
 SOURCES { } MEMORY

  41. { } MEMORY LOCAL STORAGE ORBIT COMMON LIB 
 SOURCES

  42. { } MEMORY JSON API LOCAL STORAGE ORBIT COMMON LIB

    
 SOURCES
  43. { } MEMORY JSON API LOCAL STORAGE + MORE +

    ORBIT COMMON LIB 
 SOURCES
  44. { } MODELS ORBIT COMMON LIB 
 SCHEMA

  45. { } MODELS RELATIONSHIPS ORBIT COMMON LIB 
 SCHEMA

  46. { } MODELS KEYS RELATIONSHIPS ORBIT COMMON LIB 
 SCHEMA

  47. { } MODELS KEYS RELATIONSHIPS + MORE + ORBIT COMMON

    LIB 
 SCHEMA
  48. ember-orbit

  49. Provides a loose wrapper around an OC.Source: App.LocalStorageSource = EO.Source.extend({!

    orbitSourceClass: OC.LocalStorageSource,! orbitSourceOptions: {! namespace: “myApp” // n.s. for localStorage! }! }); EO.Source ember-orbit
  50. • Extends EO.Source • Maintains an identity map of model

    instances. • Familiar interfaces to access and request data. EO.Store ember-orbit
  51. EO.Store all filter retrieve ! find add remove patch findLink

    addLink removeLink { } { } Synchronous Asynchronous ember-orbit
  52. • Defines schema with attributes and relationships • Backed directly

    by data in the source • Familiar interfaces to access and request data EO.Model ember-orbit
  53. Star = EO.Model.extend({! name: attr('string'),! planets: hasMany('planet', {inverse: 'sun'})! });!

    ! Planet = EO.Model.extend({! name: attr('string'),! classification: attr('string'),! sun: hasOne('star', {inverse: 'planets'})! }); EO.Model ember-orbit
  54. Star = EO.Model.extend({! name: attr('string'),! planets: hasMany('planet', {inverse: 'sun'})! });!

    ! Planet = EO.Model.extend({! name: attr('string'),! classification: attr('string'),! sun: hasOne('star', {inverse: 'planets'})! }); EO.Model ember-orbit
  55. EO.Model get set ! remove patch findLink addLink removeLink findLinked

    { } { } Synchronous Asynchronous ember-orbit
  56. store.add(“planet”, {name: “Jupiter”}).then(! function(jupiter) {! jupiter.get(“moons”).pushObject(io);! jupiter.set(“classification”, “gas giant”);! }!

    );! ! store.then(function() { // all requests resolve! console.log(io.get(“planet.name”)); // Jupiter! });! EO.Model ember-orbit
  57. URLS DRIVE APPLICATION STATE

  58. SOURCES DRIVE MODEL STATE ember-orbit

  59. DEVELOPMENT PATTERNS WITH ember-orbit

  60. CLIENT-FIRST DEVELOPMENT ember-orbit

  61. ember-orbit // Start development with just a local store! var

    LocalStore = EO.Store.extend({! orbitSourceClass: OC.LocalStorageSource,! orbitSourceOptions: { namespace: “myApp” }! });! ! App.initializer({! name: 'injectStore',! initialize: function(container, application) {! Orbit.Promise = Ember.RSVP.Promise;! application.register('schema:main', EO.Schema);! application.register('store:main', LocalStore);! application.inject('controller', 'store', 'store:main');! application.inject('route', 'store', 'store:main');! }! });
  62. PLUGGABLE SOURCES IndexedDB LocalStorage ember-orbit

  63. ember-orbit // Prefer IndexedDB, but default to LocalStorage! function localStoreClass()

    {! if (window.indexedDB) {! ! return IndexedDBSource; // Coming soon!! } else {! return LocalStorageSource;! }! }! ! App.initializer({! name: 'injectStore',! initialize: function(container, application) {! application.register('store:main', localStoreClass());! }! });
  64. DATA SYNCHRONIZATION ember-orbit

  65. ember-orbit // Create transform connectors between sources! // Typically performed

    in the initializer! function connectSources(container) {! var main = container.lookup('store:main').orbitSource;! var api = container.lookup('source:api').orbitSource;! ! new Orbit.TransformConnector(main, api);! new Orbit.TransformConnector(api, main);! }!
  66. EDITING CONTEXTS ember-orbit

  67. ember-orbit export default Ember.Controller.extend({! // Configure editing context (WIP!!!!!)! beginEditing:

    function(model) {! var _this = this;! var store = this.get('store');! var storeContext = store.createContext();! ! this.set('model', model);! ! storeContext.find('contact', 
 this.get('model.id')).then(function(contact) {! _this.set('editableModel', contact);! });! }! ! // Continued . . . WIP!
  68. ember-orbit // . . . Continued! ! actions: {! save:

    function() {! var _this = this;! this.get(‘editableModel.store')! .commitTransaction().then(function() {
 _this.transitionToRoute('contact.index');! })! },! ! cancel: function() {! this.transitionToRoute('contact.index');! }! }! }); WIP!
  69. UNDO / REDO ember-orbit

  70. ember-orbit ! ! // Watch for transforms and track them

    in an array! var transforms = this.get('transformations');! ! store.orbitSource.on('didTransform', ! function(operation, inverse) {! if (!_this.get('undoing')) {! transforms.pushObject(Transformation.create({! operation: operation,! inverse: inverse! }));! }! }! );! ! // Continued . . .! WIP!
  71. ember-orbit ! // . . . Continued! ! undo: function()

    {! var transformation = this.get('transformations').popObject();! var inverseOps = transformation.inverse;! var orbitSource = this.get('store.orbitSource');! var _this = this;! ! this.set('undoing', true);! orbitSource.transform(inverse).then(function() {! _this.set('undoing', false);! });! }! WIP!
  72. STATUS • Initial release January 9, 2014 • Current: 0.5.1

    ember-orbit • Initial release June 17, 2014 • Current: 0.3.0
  73. RECENT UPDATES • Full class system • Primary + secondary

    keys • Custom keys per model • Default settings for models • __meta key for source-specific data • Full JSON API support • Serializers (including JSON API)
  74. TODOS ember-orbit • Fully encapsulate Orbit’s API • Improve ergonomics

    • Add-on for ember-cli • More sources • More synchronization strategies • Coalescing / managing transforms
  75. COMING SOON orbitjs.com API docs and guides for Orbit and

    Ember-Orbit Follow progress: Twitter: @orbitjs
 IRC: #orbitjs
 Github: https://github.com/orbitjs
  76. ALSO COMING SOON Example app using JSON API Backed by

    Rails app built with JSONAPI::Resources.
 
 http://github.com/cerebris/jsonapi-resources
  77. THANKS! @dgeb