Slide 1

Slide 1 text

Brainstem Your companion for rich Rails APIs Andrew Cantino @tectonic

Slide 2

Slide 2 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 3

Slide 3 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 4

Slide 4 text

You want an API

Slide 5

Slide 5 text

You want an API for your users you know...

Slide 6

Slide 6 text

You want an API for your users’ users for your users you know...

Slide 7

Slide 7 text

You want an API for your users’ users for your users for yourselves you know...

Slide 8

Slide 8 text

You want a consistent API

Slide 9

Slide 9 text

You want a consistent API dates

Slide 10

Slide 10 text

You want a consistent API dates IDs

Slide 11

Slide 11 text

You want a consistent API dates IDs routes

Slide 12

Slide 12 text

You want a consistent API dates IDs routes JSON

Slide 13

Slide 13 text

You want a versioned API

Slide 14

Slide 14 text

You want a fast API

Slide 15

Slide 15 text

a fast API

Slide 16

Slide 16 text

a fast API

Slide 17

Slide 17 text

a fast API Is close to the database

Slide 18

Slide 18 text

a fast API Is close to the database Side-loads associations in a single request (no N+1)

Slide 19

Slide 19 text

a fast API Is close to the database Side-loads associations in a single request (no N+1) Avoids object repetition

Slide 20

Slide 20 text

a fast API Is close to the database Side-loads associations in a single request (no N+1) Avoids object repetition Allows for expressive filtering to avoid excess loads

Slide 21

Slide 21 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 22

Slide 22 text

Displaying a post

Slide 23

Slide 23 text

Displaying a post with associated data...

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Project Title Post Recipients The Post Attachment Linked Task Reply poster Reply

Slide 26

Slide 26 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } Option One

Slide 27

Slide 27 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } That’s great, but... Option One

Slide 28

Slide 28 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } • What if I want other information too? That’s great, but... Option One

Slide 29

Slide 29 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } • What if I want other information too? •What if I want to use this somewhere else? That’s great, but... Option One

Slide 30

Slide 30 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } • What if I want other information too? •What if I want to use this somewhere else? • What if I’m requesting lots of Posts at once? That’s great, but... Option One

Slide 31

Slide 31 text

{ "project_title": "RailsConf 2013", "private": true, "recipients": ["Jeff Moore", "Roger Neel", "You"], "text": "Hey guys, I'm working on the RailsConf 2013 slides now.", "linked_task": "Make presentation", "attachments": [ { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB" } ], "replies": [ { "from": "Jeff Moore", "text": "Looks like a great start! :thumbsup:", } ] } • What if I want other information too? •What if I want to use this somewhere else? • What if I’m requesting lots of Posts at once? • And where did my awesome relational model go? That’s great, but... Option One

Slide 32

Slide 32 text

{ "posts": { "25": { "project_id": "5", "private": true, "user_id": "45", "recipient_ids": ["1", "7", "45"], "text": "Hey guys, I'm working on RailsConf 2013 slides now.", "task_id": "7", "attachment_ids": ["1"], "reply_ids": ["9"] } } } Option Two

Slide 33

Slide 33 text

{ "posts": { "25": { "project_id": "5", "private": true, "user_id": "45", "recipient_ids": ["1", "7", "45"], "text": "Hey guys, I'm working on RailsConf 2013 slides now.", "task_id": "7", "attachment_ids": ["1"], "reply_ids": ["9"] }, "9": { "user_id": "7", "project_id": "5", "parent_id": "25", "private": true, "text": "Looks like a great start! :thumbsup:" } } } Option Two

Slide 34

Slide 34 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two

Slide 35

Slide 35 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two

Slide 36

Slide 36 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two • It seems longer, but it’s more reusable.

Slide 37

Slide 37 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two • It seems longer, but it’s more reusable. • It will end up being more consistent.

Slide 38

Slide 38 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two • It seems longer, but it’s more reusable. • It will end up being more consistent. • We got our awesome relational model back!

Slide 39

Slide 39 text

{ "projects": { "5": { "title": "RailsConf 2013", ... } }, "users": { "1": { "name": "Roger Neel", ... }, "7": { "name": "Jeff Moore", ... }, "45": { "name": "Andrew Cantino", ... } }, "tasks": { "7": { "title": "Make presentation", ... } }, "attachments": { "1": { "title": "RailsConf 2013 Talk", "type": "key", "size": "240.33KB", ... } } } Option Two • It seems longer, but it’s more reusable. • It will end up being more consistent. • We got our awesome relational model back! •And we get it all in one request! /api/v1/posts/25.json? include=project,user,recipients,task,attachments,replies

Slide 40

Slide 40 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 41

Slide 41 text

Presenter? AR { }

Slide 42

Slide 42 text

module Api module V1 class PostPresenter < Brainstem::Presenter presents "Post" def present(post) { :text => post.text, :private => post.private?, :created_at => post.created_at } end end end end Presenters

Slide 43

Slide 43 text

def present(post) { :text => post.text, :private => post.private?, :created_at => post.created_at, :replies => association(:replies), :task => association(:task) :attachments => association(:attachments) :user => association(:user) :recipients => association(:recipients, :json_name => "users"), :project => association(:project) } end Presenters with associations

Slide 44

Slide 44 text

module Api module V1 class UserPresenter < Brainstem::Presenter presents "User" def present(user) { :name => user.name, :email => user.email, :posts => association(:posts) } end end end end Presenters with associations

Slide 45

Slide 45 text

module Api module V1 class PostsController < ApplicationController include Brainstem::ControllerMethods def index render :json => present("posts") { # Post.relavent_for_api # Post.visible_to(current_user) Post.unscoped } end Presenters from Rails /api/v1/posts.json?include=user,... /api/v1/posts.json?only=5,8,25 /api/v1/posts.json?per_page=20&page=2

Slide 46

Slide 46 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 47

Slide 47 text

module Api module V1 class PostPresenter < Brainstem::Presenter presents "Post" sort_order :updated_at, "updated_at" sort_order :created_at, "created_at" default_sort_order "updated_at:desc" Sorting /api/v1/posts.json?order=created_at:asc

Slide 48

Slide 48 text

module Api module V1 class PostPresenter < Brainstem::Presenter presents "Post" filter :task_id do |scope, task_id| scope.where(:task_id => task_id.to_i) end filter :popular Filtering with lambdas /api/v1/posts.json?task_id=5 /api/v1/posts.json?popular=true

Slide 49

Slide 49 text

module Api module V1 class PostPresenter < Brainstem::Presenter presents "Post" filter :include_private_posts, :default => true do |scope, bool| bool ? scope : scope.only_public_posts end Filtering with defaults /api/v1/posts.json /api/v1/posts.json?include_private_posts=false

Slide 50

Slide 50 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 51

Slide 51 text

Brainstem + Backbone.js

Slide 52

Slide 52 text

Brainstem.js

Slide 53

Slide 53 text

@data = new Brainstem.StorageManager() @data.addCollection 'posts', Collections.Posts @data.addCollection 'projects', Collections.Projects @data.addCollection 'users', Collections.Users Brainstem.StorageManager The StorageManager is an object identity map and data loader for Backbone.

Slide 54

Slide 54 text

class Models.Post extends Brainstem.Model paramRoot: 'post' brainstemKey: 'posts' urlRoot: '/api/v1/posts' @associations: project: "projects" replies: ["posts"] user: "users" recipients: ["users"] task: "tasks" attachments: ["attachments"] class Collections.Posts extends Brainstem.Collection model: Models.Post url: '/api/v1/posts' Relational Models in JS

Slide 55

Slide 55 text

class Views.Posts.IndexView extends Backbone.View template: JST["backbone/templates/posts/index"] initialize: -> @collection = base.data.loadCollection "posts", include: ["project", "user", "recipients", "attachments" ...] @collection.bind 'reset', @addAll @collection.bind 'remove', @addAll render: => @$el.html @template() if @collection.loaded @addAll() else @$("#post-list").text "Just a moment..." @ Brainstem.StorageManager

Slide 56

Slide 56 text

class Views.Posts.IndexView extends Backbone.View template: JST["backbone/templates/posts/index"] initialize: -> @collection = base.data.loadCollection "posts", include: ["project", "user", "recipients", "attachments" ...] @collection.bind 'reset', @addAll @collection.bind 'remove', @addAll render: => @$el.html @template() if @collection.loaded @addAll() else @$("#post-list").text "Just a moment..." @ Brainstem.StorageManager

Slide 57

Slide 57 text

class Views.Posts.IndexView extends Backbone.View template: JST["backbone/templates/posts/index"] initialize: -> @collection = base.data.loadCollection "posts", include: ["project", "user", "recipients", "attachments" ...] @collection.bind 'reset', @addAll @collection.bind 'remove', @addAll render: => @$el.html @template() if @collection.loaded @addAll() else @$("#post-list").text "Just a moment..." @ Brainstem.StorageManager

Slide 58

Slide 58 text

base.data.loadModel "users", user_id, include: ["posts"] Brainstem.StorageManager Load single models: filters = { email: email, something: "else" } base.data.loadCollection "users", filters: filters Run filters:

Slide 59

Slide 59 text

Relational Models in JS

Slide 60

Slide 60 text

Relational Models in JS •Brainstem.js extends Backbone with relational models.

Slide 61

Slide 61 text

Relational Models in JS •Brainstem.js extends Backbone with relational models. •The StorageManager ensures that any associations you ask for are available in the identity map.

Slide 62

Slide 62 text

// Getting a post’s user’s name. post.get("user").get("name") // Looping over a post’s replies. for reply in post.get("replies") console.log reply.get("text") Relational Models in JS

Slide 63

Slide 63 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 64

Slide 64 text

{ "posts": { "25": { "project_id": "5", "private": true, "user_id": "45", "recipient_ids": ["1", "7", "45"], "text": "Hey guys, I'm working on RailsConf 2013 slides now.", "task_id": "7", "attachment_ids": ["1"], "reply_ids": ["9"] }, "9": { "user_id": "7", "project_id": "5", "parent_id": "25", "private": true, "text": "Looks like a great start! :thumbsup:" } } } IDs as keys

Slide 65

Slide 65 text

{ "posts": { "25": { "project_id": "5", "private": true, "user_id": "45", "recipient_ids": ["1", "7", "45"], "text": "Hey guys, I'm working on RailsConf 2013 slides now.", "task_id": "7", "attachment_ids": ["1"], "reply_ids": ["9"] }, "9": { "user_id": "7", "project_id": "5", "parent_id": "25", "private": true, "text": "Looks like a great start! :thumbsup:" } } } IDs as keys

Slide 66

Slide 66 text

{ "results": [ {"key": "posts", "id": "25"}, {"key": "posts", "id": "47"}, ... ], "posts": { "25": { "project_id": "5", "private": true, ... }, "47": { Results Array

Slide 67

Slide 67 text

{ "results": [ {"key": "posts", "id": "25"}, {"key": "posts", "id": "47"}, ... ], "posts": { "25": { "project_id": "5", "private": true, ... }, "47": { Results Array • Allows us to declare exactly which results, in what order, matched the user’s query. • Models can have associations of their own type. This leads to confusion if primary objects and associations are mixed in a single structure.

Slide 68

Slide 68 text

Filters in Presenters, as scopes

Slide 69

Slide 69 text

Filters in Presenters, as scopes • Most other presenter libraries leave filters to the user.

Slide 70

Slide 70 text

Filters in Presenters, as scopes • Most other presenter libraries leave filters to the user. • We wanted to head in the direction of requesting nested, filtered associations.

Slide 71

Slide 71 text

Filters in Presenters, as scopes • Most other presenter libraries leave filters to the user. • We wanted to head in the direction of requesting nested, filtered associations. • Easier to version filters in the presenter.

Slide 72

Slide 72 text

This Talk • What do you want in an API? • Why we built Brainstem • Presenters and Associations • Filters and Sorts • Brainstem.js • Decisions and Challenges • Other Libraries

Slide 73

Slide 73 text

ActiveModel::Serializers

Slide 74

Slide 74 text

ActiveModel::Serializers • More mature DSL for specifying fields

Slide 75

Slide 75 text

ActiveModel::Serializers • More mature DSL for specifying fields • Focuses on presenting, leaves filtering, sorting, and pagination up to you.

Slide 76

Slide 76 text

ActiveModel::Serializers • More mature DSL for specifying fields • Focuses on presenting, leaves filtering, sorting, and pagination up to you. • Collaboration in our future?

Slide 77

Slide 77 text

Brainstem

Slide 78

Slide 78 text

Brainstem • A presenter library for model serialization

Slide 79

Slide 79 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs

Slide 80

Slide 80 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs • An optional adaptor for Backbone

Slide 81

Slide 81 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs • An optional adaptor for Backbone • Your API will be sortable, filterable, and will side- load associations, which means:

Slide 82

Slide 82 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs • An optional adaptor for Backbone • Your API will be sortable, filterable, and will side- load associations, which means: • Shorter response times

Slide 83

Slide 83 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs • An optional adaptor for Backbone • Your API will be sortable, filterable, and will side- load associations, which means: • Shorter response times • Fewer requests

Slide 84

Slide 84 text

Brainstem • A presenter library for model serialization • An API abstraction for building powerful APIs • An optional adaptor for Backbone • Your API will be sortable, filterable, and will side- load associations, which means: • Shorter response times • Fewer requests • Happier developers!

Slide 85

Slide 85 text

https://github.com/mavenlink/brainstem https://github.com/mavenlink/brainstem-js

Slide 86

Slide 86 text

Any Questions? Andrew Cantino @tectonic andrewcantino.com https://github.com/mavenlink/brainstem https://github.com/mavenlink/brainstem-js