Api's and ember js

5,417 views

Published on

This is my #MagmaConf presentation, I talk about API's, Ruby on Rails and EmberJS

0 Comments
9 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,417
On SlideShare
0
From Embeds
0
Number of Embeds
396
Actions
Shares
0
Downloads
0
Comments
0
Likes
9
Embeds 0
No embeds

No notes for slide

Api's and ember js

  1. 1. API’s and EmberJSEdwin CruzMagmaConf 2013, Manzanillo, Colima
  2. 2. Plan• API best practices• Better API’s• EmberJS• Lessons learned
  3. 3. GET /api/products #List{"products" :[{"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} },{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }]}REST + CRUD
  4. 4. REST + CRUDGET /api/products/2 #Show{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }
  5. 5. REST + CRUDPOST /api/products #Create{"name" : "Product 2"}
  6. 6. REST + CRUDPUT /api/products/2 #Update{"name" : "Product dos"}
  7. 7. REST + CRUDDELETE /api/products/2 #Delete
  8. 8. API VERSIONING• API Internal (internal apps)• API External (mobile app ? )• API Public (general public)
  9. 9. API VERSIONING/int/api/products/ext/api/products/pub/api/products
  10. 10. API VERSIONING/int/api/products?version=2/ext/api/products?version=2/pub/api/products?version=2
  11. 11. API VERSIONING/v2/int/api/products/v2/ext/api/products/v2/pub/api/products
  12. 12. API VERSIONINGhttp://int.myapp.com/productshttp://api.myapp.com/products
  13. 13. IDEAL WORLD
  14. 14. IDEAL WORLDCorrect usage of: Accept Header
  15. 15. IDEAL WORLDCorrect usage of: Accept HeaderAccept: application/vnd.mycompany.com;
  16. 16. IDEAL WORLDCorrect usage of: Accept HeaderAccept: application/vnd.mycompany.com;version=2,application/json
  17. 17. HTTP CODES200 OK201 Created202 Accepted400 Bad Request401 Unauthorized402 Payment Required404 Not Found409 Conflict422 Unprocessable Entity500 Internal Server Error503 Service Unavailable
  18. 18. HTTP CODESHTTP/1.1 200 okGET /v1/products{"products" :[{"product" : { "id" : 1, "name" : "Product 1", "status" : "archived"} },{"product" : { "id" : 2, "name" : "Product 2", "status" : "active" } }]}
  19. 19. HTTP CODESHTTP/1.1 201 CreatedPOST /v1/products{“product”: [“name” : “name”]}
  20. 20. HTTP CODESHTTP/1.1 400 Bad Request{“errors”: [“Invalid JSON structure”,“unexpected TSTRING, expected ‘}’ atline 2”]}
  21. 21. HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“access denied”]}
  22. 22. HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“api_key not valid”,“api_key can’t contain spaces”]}
  23. 23. HTTP CODESHTTP/1.1 401 Unauthorized{“errors”: [“api_key not found, please visithttp://account.myapp.com/api and register”]}
  24. 24. HTTP CODESHTTP/1.1 422 Unprocessable Entity{“errors”: [“vendor_code: can’t be blank”],“doc”: [“vendor_code”: [“description” : “Vendor code”,“format” : “Three letters followed by 4 digits”,“example” : “SOL1234”]]}
  25. 25. HTTP CODESHTTP/1.1 500 Internal Server Error{“exception”: [“Database not available”]}
  26. 26. CODIGOS HTTPHTTP/1.1 503 Service Unavailable{“messages”: [“Under maintenance”]}
  27. 27. Better API’s• Reduce number of requests• DRY• Cacheable• Scalable
  28. 28. Better API’s{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
  29. 29. Better API’s{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: Super Vendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
  30. 30. Better API’s{ products: [{product: {id: 1,name: Product 1,vendor: {id: 3,name: Super Vendor},variants: []}},{product: {id: 99,name: Product 99,vendor: {id: 3,name: Super Vendor},variants: []}}]}GET /api/products #List
  31. 31. Better API’s{ products: [{product: {id: 1,name: Product 1,vendor: {id: 3,name: Super Vendor},variants: []}},{ product: {id: 99,name: Product 99,vendor: {id: 3,name: Super Vendor},variants: []}}]}GET /api/products #List
  32. 32. Better API’s{ products: [{product: {id: 1,name: Product 1,vendor_id: 3,variants: []}},{product: {id: 99,name: Product 99,vendor_id: 3,variants: []}}],vendors: [{id: 3,name: Super Vendor}]}GET /api/products #List
  33. 33. Better API’s{ products: [{product: {id: 1,name: Product 1,vendor_id: 3,variants: []}},{product: {id: 99,name: Product 99,vendor_id: 3,variants: []}}],vendors: [{id: 3,name: SuperVendor}]}GET /api/products #List
  34. 34. Better API’s{ products: [{ id: 1,name: Product 1,vendor_id: 3,variants: []},{ id: 99,name: Product 99,vendor_id: 3,variants: []}],vendors: [{id: 3,name: SuperVendor}]}GET /api/products #List
  35. 35. Better API’sGET /api/products #List{ products: [{ id: 1,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}
  36. 36. Caching
  37. 37. Better API’sGET /api/products #List{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}
  38. 38. Better API’sGET /api/products #List{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5] },{id : 4, size:‘M’, property_ids: [5, 6, 7] }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6] },{id : 6, size:‘M’, property_ids: [7] }}],properties: [{id: 3, name: Cotton},{id: 4, name: Water Resistant},{id: 5, name: Imported},{id: 6, name: Special},{id: 7, name: Super Property},]}Russian Doll Caching!!!
  39. 39. Better API’sdef indexif stale?(SizeRun.count)respond_with SizeRun.allendendAutomatic Etag/last_modified generation!!!Rails to the rescue!
  40. 40. Better API’sRails to the rescue!First load Following loads304 Not Modified
  41. 41. Better API’sCounter Caches{ products: [{ id: 1,version: 1369972820,variants: [ {id : 3, size:‘S’, property_ids: [3, 4, 5], inventory_units_on_hand_count: 5 },{id : 4, size:‘M’, property_ids: [5, 6, 7], inventory_units_on_hand_count: 0 }},{ id: 99,version: 1369886447,variants: [ {id : 5, size:‘S’, property_ids: [6], inventory_units_on_hand_count: 15 },{id : 6, size:‘M’, property_ids: [7], inventory_units_on_hand_count: 500 }}]}
  42. 42. Better API’sCounter Cachesclass InventoryUnit < ActiveRecord::Basebelongs_to :variant,:counter_cache => :inventory_units_on_hand_count,:conditions => "status = ‘on_hand’"end
  43. 43. Caching with Solr
  44. 44. Solr:SolrTM is the popular, blazing fast opensource enterprise search platform fromthe Apache LuceneTMproject. Its majorfeatures include powerful full-textsearch, hit highlighting, facetedsearch, near real-time indexing,dynamic clustering, databaseintegration, rich document (e.g., Word,PDF) handling, and geospatial search.
  45. 45. Sunspot - Solr• Solr powerful interface• Expressive DSL• Easily pluggable in to any ORM
  46. 46. Sunspot - Solrclass Post < ActiveRecord::Basesearchable dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
  47. 47. Sunspot - Solrclass Post < ActiveRecord::Basesearchable dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
  48. 48. Sunspot - Solrclass Post < ActiveRecord::Basesearchable(auto_index: false) dotext :title, :bodytext :comments docomments.map { |comment| comment.body }endboolean :featuredinteger :blog_idinteger :expensive_operationinteger :category_ids, :multiple => truedouble :average_ratingtime :published_attime :expired_atstring :sort_title dotitle.downcase.gsub(/^(an?|the)/, )endendend
  49. 49. Sunspot - SolrPost.search dofulltext best pizzawith :blog_id, 1with(:published_at).less_than Time.noworder_by :published_at, :descpaginate :page => 2, :per_page => 15facet :category_ids, :author_idend
  50. 50. Sunspot - Solr• Results• Contains AR instances• ‘Lazy’ loading• Hits• Solr indexed data• super fast accessresults vs hits
  51. 51. Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef method_missing(method, *args, &block)beginhit.instance.send(method)rescuehit.stored(method)endendend
  52. 52. Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef method_missing(method, *args, &block)beginif setup.stored_fields(method).empty?hit.instance.send(method)elsehit.stored(method)endrescuehit.instance.send(sym, *args)endendend
  53. 53. Sunspot - Solrresults vs hitsclass ResultProxy < ResultHitdef value(of)hit.stored(of)enddef has_property? name!setup.stored_fields(name).empty?endend
  54. 54. Sunspot - SolrAssociations?def method_missing method, *args@solr_instance ||= Search::Collection.new(Program.search { with(:id, id)}).firstif @solr_instance && @solr_instance.has_property?(method)@solr_instance.value(method)elsesuper method, *argsendend$ model_active_record = Model.find(id)$ model_active_record.association.value_stored_in_solr
  55. 55. Serializers
  56. 56. Serializers• ActiveModel::Serializer• Rabl• JSON Builder• render json: hash
  57. 57. Serializers• ActiveModel::Serializer <= no yet,WIP• Rabl• JSON Builder• render json: hash
  58. 58. Serializers• ActiveModel::Serializer• Rabl• JSON Builder <= Good enough• render json: hash
  59. 59. Serializers• ActiveModel::Serializer• Rabl• JSON Builder• render json: hash <= Hipster
  60. 60. Serializers• ActiveModel::Serializer•Rabl <= Good enough with cache• JSON Builder• render json: hash
  61. 61. Serializers{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #List
  62. 62. Serializers{ products: [{product: {id: 1,name: Product 1,status: active,code: PRO3455,vendor: {id: 3,name: SuperVendor},variants: [variant: { sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},variant: { sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]}}]}GET /api/products #ListNo ideal for real world
  63. 63. Serializers{meta: [{"page": 1,"per_page": 100,"pages": 708,"total": 70772}],"products": [{.},{.},{.},{.}],"suppliers": [{.},{.},{.},{.}],"properties": [{.},{.},{.},{.}],}GET /api/products #ListIdeal for real world
  64. 64. Serializers#index.rablobject falsenode(:style_search) do[{page: @search_results.results.current_page,total_pages: @search_results.results.total_pages,per_page: @search_results.results.per_page,}]endnode :products do@search_results.resultsendnode :suppliers do@suppliersendnode :properties do@propertiesendGET /api/products #ListIdeal for real world
  65. 65. EmberJSA javascript framework for creating ambitious webapplications.
  66. 66. Modern Web 2.0Problems• Data binding• Real time updates• Performance• Patterns• Scalability
  67. 67. Modern Web 2.0ProblemsLogo Login InfoProfile InfoLogin InfoMenuList of items with edit optionsFooterFooterFooter
  68. 68. Modern Web 2.0ProblemsLogo Currently selected Login InfoProfile InfoLogin InfoMenuList of items with edit options TopViewedFooterFooterFooter
  69. 69. EmberJS• Model• Router• Controller• View• Template
  70. 70. EmberJS - Models• Handled by Ember Data• Library to manage remote data• Acts as ‘ORM’• Transactional
  71. 71. EmberJS - ModelsMyApp.Variant = DS.Model.extend({sku: DS.attr(string),size: DS.attr(string),unitsOnHand: DS.attr(string),product: DS.belongsTo(MyApp.Product)})variants: [{ sku: AAAA1, size: S, units_on_hand: 1, product_id: 1},{ sku: AAAA2, size: M, units_on_hand: 3, product_id: 1},]
  72. 72. EmberJS - Observers• .property• computed properties• .observes• reacts when something happens• bindings• ensure objects in multiple places are in
  73. 73. EmberJS - ObservesMyApp.SavingMessage = Ember.Mixin.create({observeSaving: function() {if(this.get(isSaving) && !this.get(queueSavingMessage)) {this.set(queueSavingMessage, Ember.flashQueue.pushFlash(spindicator, Saving, true, true));} else {if(this.get(queueSavingMessage)) {this.set(queueSavingMessage.specialFlag, false);this.set(queueSavingMessage, null);}}}.observes(isSaving)});MyApp.Program = DS.Model.extend(MyApp.SavingMessage, {........}
  74. 74. EmberJS - Models/bindings• isLoaded• isReloading• isDirty• isSaving• isDeleted• isError• isNew• isValid
  75. 75. EmberJS - Models/callbacks• didLoad• didReload• didUpdate• didCreate• didDelete• becameInvalid• becameError
  76. 76. EmberJS - Models/callbacksvar product = MyApp.Product.createRecord();product.on(didCreate, this, function() {console.log(‘Product created’);});
  77. 77. EmberJS - Modelsvar MyAppAdapter = DS.RESTAdapter.extend();MyAppAdapter.configure(MyApp.ProductSearch, {sideloadAs: products});MyAppAdapter.configure(MyApp.Properties, {sideloadAs: properties});MyAppAdapter.configure(MyApp.Supplier, {sideloadAs: suppliers});
  78. 78. EmberJS - ModelsMyApp.ProductsSearch = DS.Model.extend({perPage: DS.attr(number),totalPages: DS.attr(number),totalEntries: DS.attr(number),page: DS.attr(number),products: DS.hasMany(MyApp.Product)})
  79. 79. EmberJS - Models/transactionsvar transaction = this.get(‘store’).transaction();var product = transaction.createRecord(MyApp.Product, {});product.on(didCreate, this, function() {console.log(‘program created’);});transaction.commit();
  80. 80. Router
  81. 81. EmberJS - Router• EmberJS way to manage states• Each state is represented by a URL• User interaction causes URL to change• redirected to a new URL• Updates controller• Change template screen
  82. 82. EmberJS - RouterApp.Router.map(function() {this.route(index, { path: /});this.resource(program, {path: /programs/:program_id}, function(){this.resource(programStyle, {path: /styles/:style_id}, function(){this.route(newImage, { path: /image/new});this.route(purchaseInfoTab, { path: /purchase_info});});this.resource(programSku, {path: /skus/:sku_id});});});
  83. 83. EmberJS - RouterApp.Router.map(function() {this.route(index, { path: /});this.resource(program, {path: /programs/:program_id}, function(){this.resource(programStyle, {path: /styles/:style_id}, function(){this.route(newImage, { path: /image/new});this.route(purchaseInfoTab, { path: /purchase_info});});this.resource(programSku, {path: /skus/:sku_id});});});
  84. 84. Controllers
  85. 85. EmberJS - Controllers• Designed to decorate models• display logic• Properties frontend specific• they don’t need to be saved• Decorates data• Bridge between your data and your
  86. 86. EmberJS - ControllersApp.ApplicationController = Ember.Controller.extend({// the initial value of the `search` propertysearch: ,query: function() {// the current value of the text fieldvar query = this.get(search);this.transitionToRoute(search, { query: query });}});
  87. 87. Route
  88. 88. EmberJS - RouteApp.Router.map(function() {this.resource(post, { path: /posts/:id });});App.PostRoute = Ember.Route.extend({model: function(params) {return App.Post.find(params.id);},setupController: function(controller, model) {this.controllerFor(‘myPosts’, model);},renderTemplate: function() {this.render(‘betterPost’);}});
  89. 89. Views
  90. 90. EmberJS -Views• Barely used, because of handlebars• Sophisticated user events• Create re-usable components
  91. 91. EmberJS -Viewsvar view = Ember.View.create({templateName: say-hello,name: "Bob"});view.appendTo(#container);
  92. 92. EmberJS -Views/EventsMyApp.UpVoteView = Ember.View.extend({tagName: span,title: Upvote,templateName: common/upvote,click: function(evt) {this.get(controller).send(upVote);}});
  93. 93. Templates
  94. 94. EmberJS - Templates• Handlebars is used as template engine• regular HTML with embedded handlebarexpressions• Minimal Templating on Steroids• Emblem.js if you are haml lover
  95. 95. Handlebars<div class="entry"><h1>{{title}}</h1><h2>By {{author.name}}</h2><div class="body">{{body}}</div></div>
  96. 96. Gluing everything
  97. 97. EmberJS
  98. 98. EmberJSRequestRouterViewTemplateModelControllerHTMLRoute
  99. 99. EmberJSMyApp = Ember.Application.create();var MyAppAdapter = DS.RESTAdapter.extend();MyApp.Store = DS.Store.extend({revision: 12,adapter: MyAppAdapter});
  100. 100. EmberJS$ application.handlebars<header><h1>MagmaConf Talk!</h1></header><div>{{outlet}}</div>{{ outlet modal }}<footer>&copy;2013 Crowd Interactive.</footer>
  101. 101. EmberJS$ application.handlebars<header><h1>MagmaConf Talk!</h1></header><div>{{outlet}}</div>{{ outlet modal }}<footer>&copy;2013 Crowd Interactive.</footer>
  102. 102. EmberJS$products/index.handlebars{{#if content.isLoaded}}<div class="row"><table class="table table-striped table-bordered" id="programs-table"><thead><tr><th>Product Image</th><th>Product Name</th><th>Vendor</th></tr></thead><tbody>{{#each product in controller.currentPage.products}}{{ view MyApp.ProductRowView productBinding="product"}}{{else}}<td colspan="3">Your search returned no results.</td>{{/each}}</tbody></table></div>{{else}}<div class="row">Loading...</div>{{/if}}
  103. 103. EmberJS$products/index.handlebars{{#if content.isLoaded}}<div class="row"><table class="table table-striped table-bordered" id="programs-table"><thead><tr><th>Product Image</th><th>Product Name</th><th>Vendor</th></tr></thead><tbody>{{#each product in controller.currentPage.products}}{{ view MyApp.ProductRowView productBinding="product"}}{{else}}<td colspan="3">Your search returned no results.</td>{{/each}}</tbody></table></div>{{else}}<div class="row">Loading...</div>{{/if}}
  104. 104. EmberJSMyApp.ProductRowView = Ember.View.extend({templateName:products/row,tagName:tr,click:function(evt) {router = this.get(controller.target.router);router.transitionTo(product.index, this.product);}});
  105. 105. EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}
  106. 106. EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}
  107. 107. EmberJS$ products/row.handlebars{{#with product}}<td><img {{bindAttr src="primaryImageUrl"}} {{ bindAttr alt="name" }}></td><td>{{name}}</td><td>{{supplierNames}}</td>{{/with}}They will always be in sync!
  108. 108. EmberJS<ul><li {{bindAttr class="hasPrevious::disabled"}}><a href="#" {{action "previousPage"}}>&larr; previous</a></li>{{#each page in currentPage.listOfPages}}{{#with page}}<li {{bindAttr class="active"}}><a href="#" {{action changePage number}}>{{number}}</a></li>{{/with}}{{/each}}<li {{bindAttr class="hasNext::disabled"}}><a href="#" {{action "nextPage"}}> next &rarr;</a></li></ul>
  109. 109. EmberJS<ul><li {{bindAttr class="hasPrevious::disabled"}}><a href="#" {{action "previousPage"}}>&larr; previous</a></li>{{#each page in currentPage.listOfPages}}{{#with page}}<li {{bindAttr class="active"}}><a href="#" {{action changePage number}}>{{number}}</a></li>{{/with}}{{/each}}<li {{bindAttr class="hasNext::disabled"}}><a href="#" {{action "nextPage"}}> next &rarr;</a></li></ul>
  110. 110. EmberJS<h2>Editing User: {{content.name}}</h2><div class="row"><div class="span4"><label>First name</label>{{view Ember.TextField valueBinding="content.firstName"}}<label>Last name</label>{{view Ember.TextField valueBinding="content.lastName"}}<label>Email</label>{{view Ember.TextField valueBinding="content.email"}}
  111. 111. EmberJS<h2>Editing User: {{content.name}}</h2><div class="row"><div class="span4"><label>First name</label>{{view Ember.TextField valueBinding="content.firstName"}}<label>Last name</label>{{view Ember.TextField valueBinding="content.lastName"}}<label>Email</label>{{view Ember.TextField valueBinding="content.email"}}
  112. 112. EmberJSMyApp.ProductRoute = Ember.Route.extend({exit: function() {var controller = this.controllerFor(product);var content = controller.get(content);if (content && content.get(isDirty)) {content.get(transaction).rollback();}this._super();}});
  113. 113. EmberJS• Enumerables• forEach, map, mapProperty, filter,• ContainerView• String functions• camelize, capitalize, classify, dasherize, underscore, etc!• View Components• TextArea,TextField, CheckBox• etc
  114. 114. EmberJSHopefully I didn’t confuse you... more
  115. 115. Thanks!Questions?Edwin Cruzedwin@crowdint.com@softr8

×