Virtual Falcor Server Falcor Falcor Server var member = new falcor.Model({source: var member = new falcor.Model({cache:{ var member = new falcor.Model({cache:{ var member = new falcor.Model({cache:{ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ router: new falcor.Router([ new falcor.HttpSource( “ member.json ”) }); name: “Steve McGuire” , name: “Steve McGuire”, name: “Steve McGuire”, { { { occupation : “Developer”, occupation: “Developer”, occupation: “Developer”, route: [“member”,[“name”,”occupation”]] , route: [“member”,[“name”,”occupation”]] , route: [“member”,[“name”,”occupation”]] , get: (pathSet) => get: (pathSet) => get: (pathSet) => location: { location: { location: { memberDB . memberDB . country: “US”, country: “US”, country: “US”, exec(`SELECT ${pathSet [1].join(‘,’)} exec(`SELECT ${pathSet [1].join(‘,’) } city: “Pacifica”, city: “Pacifica”, city: “Pacifica”, FROM user FROM user WHERE id = ${request.cookies.userid}`) WHERE id = ${request.cookies.userid}`) address: “344 Seaside” address: “344 Seaside” address: “344 Seaside” }, }, }, } } } { { { route: [“member”,“location”,[“country”, ”city”, “address”]] , route: [“member”,“location”,[“country”, ”city”, “address”]] , route: [“member”,“location”,[“country”, ”city”, “address”]] , }}); }}); }}); get: (pathSet) => get: (pathSet) => get: (pathSet) => locationServer. member.get ( [“location”, “ address ”], (new falcor.HttpServer(member)). (new falcor.HttpServer(member)). (new falcor.HttpServer(member)). getLocation(request.cookies.userid). then(location => ({ (address) => print(address)). listen(80); listen(80); listen(80); member: { location: getProps(location, pathSet[2]) } toPromise(); }) } } } ]) ]) ]) }); }); }); (new falcor.HttpServer(member)).listen(80); (new falcor.HttpServer(member)).listen(80); (new falcor.HttpServer(member)).listen(80);
How do we do it efficiently?
Falcor Optimization • Batching • Caching • Path Optimization
Building Netflix with Falcor
Defining a Model
Netflix Member JSON Model var member = { var member = { var member = { genreLists: [ genreLists: [ genreLists: [ { { { name: "Suggestions For You", name: "Suggestions For You", name: "Suggestions For You", titleList: [ titleList: [ titleList: [ { { { id: 523, id: 523, id: 523, name: "Friends", name: "Friends", name: "Friends", rating: 5, rating: 5, rating: 5, // more fields // more fields // more fields }, }, }, // "Brain Games", "Spartacus"... // "Brain Games", "Spartacus"... // "Brain Games", "Spartacus"... ], ], ], }, }, }, // "New Releases", "British TV Shows", ... // "New Releases", "British TV Shows", ... // "New Releases", "British TV Shows", ... ] ] ] } } }
We’re almost ready to move our model into the cloud. http://netflix.com/ member.json
There’s just one problem…
Netflix’s Domain Model is a Graph
JSON is for Trees.
When we convert a graph to a JSON we get duplicates. “Suggestions for You” Genre Lists “New Releases”
Introducing JSON Graph
JSON Graph • Graph format for JSON • Tree with Symbolic Links • Every value type is a resource
JSON JSON Graph var model = { var model = { var model = { var model = { var model = { var model = { var model = { var model = { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = { var model = { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = { var model = { var model = { var model = { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = { var model = new falcor.Model({ cache: { var model = new falcor.Model ({ cache: { var model = { var model = { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = new falcor.Model({ cache: { var model = { var model = { var model = new falcor.Model({ cache: { genreLists : [ genreLists : [ genreLists: [ genreLists : [ genreLists: [ genreLists: [ genreLists: [ genreLists: { genreLists : { genreLists : { genreLists : { genreLists : { genreLists: { genreLists: { genreLists: [ genreLists: { genreLists : { genreLists: [ genreLists: [ genreLists : { genreLists: { genreLists : { genreLists: { genreLists : { genreLists: { genreLists: [ genreLists : { genreLists : { genreLists: [ genreLists: { genreLists: { genreLists : { genreLists: [ genreLists : { genreLists : { { { { { { { { “0”: { “0”: { { “0”: { “0”: { { { “0”: { “0” : { { “0”: { “0” : { “0” : { “0”: { “0” : { “0” : { “0” : { “0” : { { { “0” : { “0”: { “0” : { “0”: { { “0”: { “0” : { “0” : { name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" name: "Suggestions For You" titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: { titlesList: { titlesList : { titlesList : { titlesList: { titlesList: { titlesList: { titlesList: [ titlesList : { titlesList : { titlesList : { titlesList : { titlesList: [ titlesList: { titlesList: [ titlesList: { titlesList: { titlesList : { titlesList: { titlesList: [ titlesList: [ titlesList: [ titlesList : { titlesList : { titlesList: { titlesList: { { { { { { { { “0” : [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0” : [“ titlesById ”, 956] , [“ titlesById ”, 956] [“ titlesById ”, 956] “0” : [“ titlesById ”, 956] , “0” : [“ titlesById ”, 956] , [“ titlesById ”, 956] “0” : [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0” : [“ titlesById ”, 956] , “0” : [“ titlesById ”, 956] , { “0”: [“ titlesById ”, 956] , [“ titlesById ”, 956] “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: , { “0”: [“ titlesById ”, 956] , [ “ titlesById ”, 956 ] “0” : [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , [“ titlesById ”, 956] “0” : [“ titlesById ”, 956] , “ titlesById ”, 956 “ titlesById ”, 956 id: 956, id: 956, id: 956, id: 956, id: 956, id: 956, id: 956, length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 id: 956, id: 956 , name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends" , name: "Friends", name: "Friends", } } } } } } } } } } } } } } } } } name: "Friends", name: "Friends", rating: 5 rating: 5 rating: 3 rating: 3 rating: 5 rating: 3 rating: 3 }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, rating: 3 rating: 3 } } } } } } } “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { “1”: { } } { ] ] ] ] ] ] ] name: "New Releases", name: "New Releases", } name: "New Releases", ] } name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", } ] name: "New Releases", name: "New Releases", ] ] ] name: "New Releases", name: "New Releases", ] name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", ] ] id: 956, }, }, }, } , } , }, } , }, }, }, }, }, }, }, }, }, }, }, titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { titlesList: { name: "Friends", name: "Friends", { { { { { { { { “1”: { “1”: { { { { { “1”: { { “1”: { { “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , rating: 3 rating: 3 name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", name: "New Releases", length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 length: 75 } titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ titlesList: [ } } } titlesList: [ } titlesList: [ } } } } } titlesList: [ } titlesList: [ titlesList: { } } } titlesList: [ } } } titlesList: { titlesList: [ titlesList: { titlesList: [ } { { { { { { { { { }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, [“ titlesById ”, 956] [ “ titlesById ”, 956 ] [“ titlesById ”, 956] [“ titlesById ”, 956] [“ titlesById ”, 956] “0”: , [“ titlesById ”, 956] “0”: [“ titlesById ”, 956] , “0”: [“ titlesById ”, 956] , id: 956 , id: 956, id: 956, id: 956, id: 956, id: 956, id: 956, id: 956, id: 956, length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 50 length: 75 length : 75 length: 75 name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, }, rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 titlesById : { titlesById : { titlesById : { titlesById : { titlesById: { titlesById : { titlesById : { titlesById: { titlesById: { titlesById : { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { } } } } } } } } } “956”: { “956”: { “956” : { “956”: { “956”: { “956” : { “956” : { “956”: { “956”: { “956” : { “956” : { “956”: { “956”: { “956”: { “956”: { “956”: { “956”: { ] ] ] ] ] ] ] } ] name: "Friends", ] name: "Friends", name : "Friends", name: "Friends", name: "Friends", ] ] ] name: "Friends", name: "Friends", ] ] } name: "Friends", name: "Friends", name: "Friends", name: "Friends", ] name: "Friends", name: "Friends", } name: "Friends", name: "Friends", name: "Friends", name: "Friends", } } } } } } } } }, }, } } } } } }, }, } rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating : 5 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating : 3 rating: 3 length: 50 } } } length: 50 } } } } } } } } } } length: 50 } length: 50 } } } ] ] ] ] ] ] ] ] } } } } } } } } } }, } }, ], } } }, ], } ] , } , } } } , } } ], ], } } } } } } } }); }); }); }); }); }); }); }); } } }); }); }); }); } ); }); }); }); titlesById: { titlesById : { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { titlesById: { “956” : { “956”: { “956”: { “956”: { “956”: { “956”: { “956”: { “956”: { “956”: { “956”: { name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", name: "Friends", rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 rating: 3 } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } model[“ genreLists ”][0][" titlesList "][0][“rating”] = 5 model.get([ “ titlesById ” , 956 , “name” ]) model.get([ “ genreLists ” , 0 , "titlesList ” , 0 , “name”]) model.get([ “ titlesById ” , 956 , “name”]) model.get([ “ titlesById ” , 956, “name”]) model.get([ model.get([ model.get([ “ genreLists ” , 0 , "titlesList ” , 0, “name”]) model.get([ “ genreLists ” , 0 , "titlesList ”, 0, “name”]) model.get([ “ genreLists ” , 0, "titlesList ”, 0, “name”]) model.get ([“ genreLists ”, 0, " titlesList ”, 0, “name”]) model .get( [“ genreLists ” , 0, "titlesList ” , 0, “name”] ) model.get ([“ titlesById ”, 956, “name”]) model[“ genreLists ”][0][" titlesList "][0][“name”] “ titlesById ” ,956, “name”]) “name”]) model[“ genreLists ”][0][" titlesList "][0][“name”] model[“ genreLists ”][0][" titlesList "][0][“name”] model[ “ genreLists ” ][ 0 ][ "titlesList" ][ 0 ][ “name” ] model[“ genreLists ”][0][" titlesList "][0][“rating”] = 5 model[ “ genreLists ” ][ 0 ][ "titlesList" ][ 0 ][ “rating” ] = 5 model[“ genreLists ”][0][" titlesList "][0][“rating”] = 5 model.setValue([ “ titlesById ” , 956 , “rating”], 5) model .setValue( ["genreLists" , 0 , "titlesList" , 0 , "rating"], 5 ) model.setValue(["genreLists", 0, "titlesList", 0, "rating"], 5) model.setValue([ “ genreLists ” , 0, "titlesList ”, 0, “rating”], 5) model.setValue([ “ genreLists ” , 0 , "titlesList ”, 0, “rating”], 5) model.setValue([ “ genreLists ” , 0 , "titlesList ” , 0, “rating”], 5) model.setValue([ “ genreLists ” , 0 , "titlesList ” , 0 , “rating”], 5) model.setValue([ model.setValue([ model.setValue([ “ titlesById ” , 956, “rating”], 5) model.setValue([ “ titlesById ” , 956 , “rating” ], 5) “ titlesById ” ,956, “rating”], 5) “rating”], 5)
JSON Graph: Small Resources { genreLists: { “0”: { name: “Suggestions for You” , titlesList: { “0”: [“ titlesById ”, 956] , “1”: [“ titlesById ”, 192] , // more titles length: 75 }, }, length: 40 }, titlesById: { “956”: { name: “Friends” , rating: 5 } } }
Fine-Grained Resources JSON Graph HTTP/REST [“ genreLists ”, “length”] /genreLists [“ genreLists ”, *, name] [“ titlesById ”, 23432, “name”] /titles/23432 [“ titlesById ”, 23432, “rating” ] [“ titlesById ”, 23432, “description” ] [“ titlesById ”, 23432, “bookmark” ] [“ titlesById ”, 23432, “director” ] // more fields
Idempotent Operations Falcor Model HTTP/REST get GET set PUT
Building a Virtual Model http://netflix.com/ member.json
Virtual Model: Defining Routes var member = new falcor.Model({ router: new falcor.Router([ { route: ["genreLists", Router.INTEGERS, Router.INTEGERS], // example match: ["genreLists", [1,2,3], [1,5,2]] pathSet => { // retrieve data from member’s personalized list DB // return as JSON Graph }, }, { route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]] , // example match: [“ titlesById ”, [2343,5123], [“name”]] pathSet => { // retrieve data from title DB // return as JSON Graph } } ]) });
Title Route var member = new falcor.Model({ router: new falcor.Router([ // genre list route snipped { route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]] , // example match: [“ titlesById ”, [2343,5123], [“name”]] pathSet => { var titleIds = pathSet[1], fields = pathSet[2]; titleStore. getTitlesByIds(titleIds). then(titles => { var titlesById = {}; titles.forEach(title => { titlesById[title.id] = {}; fields.forEach(field => titlesByid[title.id][field] = title[field]); }); return { titlesById: titlesById }; }); } ]) });
Virtual Model Path Evaluation var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ router: new falcor.Router([ { source: new falcor.HttpSource('member.json ’), source: new falcor.HttpSource('member.json ’)}); { { { { { { { { { { genreLists: { cache: { route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], route: ["genreLists", Router.INTEGERS, Router.INTEGERS], “0”: { genreLists: { pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, ( [“ genreList ”],[0],[0]] ) => { /* retrieve data from lists DB */}, pathSet => { /* retrieve data from lists DB */}, “0”: [“ titlesById ”, 926] “0”: { }, }, }, }, }, }, }, }, }, }, } “0”: [“ titlesById ”, 926] { { { { { { { { { { { } } titlesById: { route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]] , route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], } }, “926”: { pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titles DB */} ( [“ titlesById ”, [926], [“ aname ”]] ) => {{ /* retrieve data */} pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titles DB */} pathSet => {{ /* retrieve data from titlesDB */} pathSet => {{ /* retrieve data from titles DB */} titlesById: { “name”: “Friends” } } } } } } } } } } “926”: { } ]), ]), ]); ]), ]) ]), ]) ]), ]), ]), name: “Friends” } }); }); cache: { cache: { cache: { cache: { cache: { cache: { cache: { cache: { } genreLists: { genreLists: { genreLists: { genreLists: { genreLists: { genreLists: { genreLists: { genreLists: { } “0” : { “0” : { “0”: { “0” : { “0” : { “0” : { “0”: { “0” : { } “0”: [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “0”: [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “0” : [“ titlesById ”, 926] “ titlesById ”, 926 }); } } } } } } } } } }, }, }, } }, } } } } } } titlesById: { titlesById: { titlesById: { titlesById: { }); }); }); }); “926”: { “926”: { “926”: { “926”: { name: “Friends” name: “Friends” name: “Friends” name: “Friends” } } } } } } } } } } } } }); }); }); member. member. member.get ([“ titlesById ”, 926, “name”]) member.get([ “ titlesById ”, 926, “name” ]) member.get(["genreLists", 0, 0, "name"]) member.get(["genreLists", 0, 0, "name"]) getValue ([“ genreLists ”, 0 , 0, “name”]). getValue ([“ genreLists ”, 0 , 0, “name”]). toPromise(); toPromise();
Virtual Model Path Evaluation var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ var member = new falcor.Model({ source: new falcor.HttpSource('member.json ’), source: new falcor.HttpSource('member.json ’), router: new falcor.Router([ source: new falcor.HttpSource('member.json ’), cache: { cache: { cache: { { genreLists: { genreLists: { genreLists: { route: ["genreLists", Router.INTEGERS, Router.INTEGERS], “0”: { “0”: { “0”: { pathSet => { /* retrieve data from lists DB */}, “0”: [“ titlesById ”, 926] “0”: [“ titlesById ”, 926] “0”: [“ titlesById ”, 926] }, } } } { }, }, }, route: [“ titlesById ”, Router.INTEGERS , [“name”, “rating”]], titlesById: { titlesById: { titlesById: { pathSet => {{ /* retrieve data from titles DB */} “926”: { “926”: { “926”: { } name: “Friends” name: “Friends” name: “Friends” ]); } } } } } } } } } }); }); }); member. member. member. getValue( [“ titlesById ”, 926, “rating”] ). getValue ([“ genreLists ”, 0, 0, “rating”]). getValue ([“ genreLists ”, 0 , 0, “name”). member.get ([“ titlesById ”, 926, “rating”]) toPromise(); toPromise(); toPromise();
The World Wide Web is Flat
The Type of the WWW Map<String, Resource>
The WWW is Flat GET http://../genreLists/0/0/name 302 http://../titles/23432/name GET http://../titles/23432/name “Friends”
The WWW is Flat (continued) GET http://../genreLists/0/0/rating 302 http://../titles/23432/rating GET http://../titles/23432/rating 5.0
No Hierarchy GET http://../genreLists/0/0/name 302 Redirect http://../genreLists/0/0/* http://../titles/23432/*
Sets Are Idempotent model.set( { path: [“titles”, 234324, “rating”], value: 5 });
Non Idempotent Operations model.call( [“genreList”,0,”add”], [[“titles”, 234234]])
Function Calls • Can invoke function that lives anywhere in Graph • Functions can only be called, not downloaded • Functions can invalidate any resource in cache
Falcor • Cache Consistency • Loose Coupling • Low Latency • Small Message Sizes
Additional Features • Automatic Cache Management – Items purged based on order in LRU – Custom size can be applied to items – One total size for all heterogenous types • Fast Dirty Checking – All set operations on leaves mark branches as dirty – Can be used for fast model diff a la immutable types
Three JavaScript UIs Mobile.js Web.js TV.js
Open-Source Soon • Should be available in the next few months • Looking for help integrating with existing MVC frameworks • @falcorjs
Questions?
JSON Graph and REST [“ videosById ”, 512] /videosById/512 [“ genreLists ”, 0, 0] /genreLists?col=0&row=0 [“ genreLists ” ,{to:5},{to:5}, /genreLists
JSON Graph • Serializable Graph • Fine-Grained
Falcor Client Falcor Server var member = new falcor.Model({source: , new falcor.HttpSource( “ member.json ”)}) ; cache: { var member = new falcor.Model({ var member = new falcor.Model({ name: “Steve McGuire”, name: “Steve McGuire”, occupation: “Developer”, occupation: “Developer”, location: { location: { location: { location: { location: { country: “US”, country: “US”, country: “US”, country: “US”, city: “Pacifica”, city: “Pacifica”, city: “Pacifica”, city: “Pacifica”, address: “344 Seaside ” address: “344 Seaside” address: “344 Seaside” } } } } } }); }); }}); member. member. (new falcor.HttpServer(member)). (new falcor.HttpServer(member)). get( [“location”, “address”] , get( [“location”,[“country”,“city”]] , listen(80); listen(80); (address) => <div>{address}></div>). (address) => <div>{address}></div>). subscribe(jsx => updateUI(jsx)); subscribe(jsx => updateUI(jsx));
/ member.json
A Simple Issue Tracking App
Issue Tracking Zip code is not validated Open and Assigned To Me Issue Issue Status Status Description Inconsistent data displayed Inconsistent data displayed Open Open Currently we are not Slow load times due to … Slow load times due to … Closed Closed validating ZIP Codes and we are seeing a lot of Caching in browser is slow.. Caching in browser is slow.. Resolved Resolved invalid data in the Zip code is not validated Zip code is not validated Open Open database. Lib added to build. Lib added to build. Switching profiles does… Switching profiles does… Closed Closed Kim Trott Assigned To 500 error incorrectly… 500 error incorrectly… Resolved Resolved Kim Trott Template not updated… Template not updated… Open Open Font is incorrect on user… Font is incorrect on user… Resolved Resolved Status Add Comment Dialog does not close… Dialog does not close… Open Open Open
Issue Tracking: Domain Model User Issue Comment
Let’s talk about REST.
REST: Issue Tracking End Points /user/{userId} /issue/{issueId} /comment/{commentId} /user/{userId}/issues /issue/{issueId}/comments
User Resource: /user/5232 { name: “Kim Trott ”, title: “Director”, twitter: “@ alwayson ”, email: “ alwayson@netflix.com, // more fields… }
Issue Resource: /issue/926 { name: “Zip code is not validated”, description: “We need to apply…”, status: “Resolved”, dueDate: 956568342, // more fields… }
Comment Resource: /comment/612 { { text: “ W hat about the Canadians? Don’t we need to text: “What about the Canadians? Don’t we need to validate postal codes?”, validate postal codes?”, user: “/user/5232” user: “ /user/5232 ” } }
User Issues: /users/5232/issues [ “/issues/926”, “/issues/696”, “/issues/651”, “/issues/2239”, ]
/issues/926/comments [ “/comments/912”, “/comments/555”, “/comments/61”, “/comments/610 ]
Issue Tracking with REST Your Issues Issue Status Inconsistent … Open Zip code is… Open Caching in us... Resolved Test harness… Open Switching prof Closed 500 error not… Resolved Template not… Open Font is incorr … Resolved
Issue Tracking with REST 0 OK “/issues/5412”, No Phone HTTP/1.0 200 OK “/issues/926”, SQ SQ HTTP L Cache L “/issues/6293”, “/issues/6102”, SELECT TOP 15 issues “/issues/9232”, FROM user R#0: GET /users/532/issues? WHERE page=15 “/issues/16”, userId = 532 name: “Inconsistent formatting “/issues/0239”, applied to dates.” , “/issues/7623”, Your Issues Your Issues Zip code is not validated R#1: GET /issues/5412 “/issues/2239”, description: “We need to apply…”, db.issues.get(5412) “/issues/512” , Description Issue Issue Issue Status Status Status status: “Open” , “/issues/523” , Currently we are not Inconsistent … Inconsistent … “/issues/9823” , R#2: GET /issues/926 Open Open dueDate: 956568342, Inconsistent … Open db.issues.get(926) validating ZIP Codes “/issues/615” , Zip code is… Zip code is… Open Open and we are seeing a lot Zip code is… Resolved // more fields… ... ... ... “/issues/2267” , of invalid data in the Caching in us... Caching in us... Resolved Resolved Caching in us... Resolved database. “/issues/612”, Test harness… Test harness… Open Open R#15: GET /issues/891 Test harness… Open db.issues.get(891) Assigned To Switching prof Switching prof Closed Closed Switching prof Closed Kim Trott 500 error not… 500 error not… Resolved Resolved db.issues. set ( 500 error not… Resolved R#16: GET /issues/926 Template not… Template not… Open Open 891, Status Template not… { Open Font is incorr … Font is incorr … Resolved Resolved status: “Resolved”, Resolved Open Font is incorr … // more properties Resolved R#17: PUT /issues/926 Open )); Resolved
REST: Cache Coherence User DB Phone HTTP Cache User User Issue DB Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue Issue
REST = Laaaaaaaattteeennccccyy
Overfetching with REST /issues/26343 { name: “Zip code is not validated”, description: “We need to apply…”, description: “We need to apply…”, status: “Resolved”, dueDate: 956568342, // more fields… }
Issue Tracking with RPC Your Issues Issue Status Inconsistent … Open Zip code is… Open Caching in us... Resolved Test harness… Open Switching prof Closed 500 error not… Resolved Template not… Open Font is incorr … Resolved
RPC: Stale Caches
RPC: Gerrymandered “Resource” Issue Issue Issue Issue User Issue issueList Issue Issue Issue Issue Issue Issue Issue
RPC: Tight Coupling Your Issues Issue Status Inconsistent … Open Issue Status Last Comment Zip code is… Open Inconsistent forma… “Could we just…” Open Caching in us... Resolved Zip code is… “Canadian postal?” Open Test harness… Open “Why manage it… Caching in us... Resolved Switching prof Closed Test harness… “Should take a Open 500 error not… Resolved while.” Template not… Open Switching profiles… “Cut loading time…” Closed Font is incorr … Resolved
REST RPC • Cache Consistency • Small Message Size • Loose Coupling • Low Latency
• Distributed Architecture for Web Applications • Support Idempotent Operations and RPC calls • Maximize Cache Utility, Minimize Message Size
More recommend