$30 off During Our Annual Pro Sale. View Details »

EmberFest 2017 - Ember @ Netflix

Lauren Tan
February 24, 2018

EmberFest 2017 - Ember @ Netflix

This talk was presented at EmberFest 2017
by Lauren Tan and Offir Golan. Recording available here -
https://pusher.com/sessions/meetup/emberfest/ember-netflix

For a few years now, Netflix has been leveraging Ember to build ambitious applications that help us manage and produce billions of dollars in content. In this talk, we'll share our lessons learned, talk about some of the common problems we've faced, and how we've solved them.

Lauren Tan

February 24, 2018
Tweet

More Decks by Lauren Tan

Other Decks in Programming

Transcript

  1. React
    @
    Netflix
    Offir Golan & Lauren Tan
    ReactFest 2017

    View Slide

  2. View Slide

  3. View Slide

  4. Lol jk

    View Slide

  5. Ember
    @
    Netflix
    Offir Golan & Lauren Tan
    EmberFest 2017

    View Slide

  6. @offirgolan
    @offirgolan
    @offirgolan

    View Slide

  7. View Slide

  8. ember-cp-validations
    ember-light-table
    ember-burger-menu
    ember-time-machine
    ember-parachute
    ember-data-copyable
    ember-require-module
    ember-validators
    ember-cli-nvd3
    ember-addon-genie
    ember-bootstrap-cp-validations
    ember-cli-inject-meta
    ember-changeset-cp-validations
    ember-cli-jsoneditor
    ember-host-manager
    ember-query-builder

    View Slide

  9. @sugarpirate_
    @poteto
    @sugarpirate

    View Slide

  10. How i got here

    View Slide

  11. I like computers

    View Slide

  12. ember-changeset
    ember-lazy-video
    ember-pipeline
    ember-composable-helpers
    ember-parachute
    ember-metrics
    ember-one-way-controls
    ember-crumbly
    ember-deep-set
    ember-in-viewport
    ember-test-component
    ember-cli-flash
    ember-changeset-validations
    ember-api-feature-flags
    ember-route-action-helper
    ember-macaroni

    View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. WHY EMBER?

    View Slide

  22. Over 20
    large apps

    View Slide

  23. Studio Apps
    Wrap
    Pitch Negotiation Production
    Prep Post
    Origin Story
    Watson
    Budget
    Gravity
    Murdock
    Jet
    Moneyball
    Amadeus
    Orion
    Sherlock

    View Slide

  24. Authentication ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
    Data Loading ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
    Shared Data Models ✓ ✓ ✓ ✓ ✓ ✓ ✓
    Data Tables ✓ ✓ ✓ ✓ ✓ ✓ ✓
    Data Entry ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
    Deployment ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓

    View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. AUTHENTICATION

    View Slide

  31. View Slide

  32. BOLT UI

    View Slide

  33. View Slide

  34. Bolt Meechum
    GET /users
    Authorization Bearer ...

    View Slide

  35. View Slide

  36. View Slide

  37. import DS from 'ember-data';
    import MeechumProxyMixin from 'ember-cli-meechum-proxy/mixins/adapters/meechum-proxy';
    export default DS.JSONAPIAdapter.extend(MeechumProxyMixin, {
    // ...
    });

    View Slide

  38. export default Service.extend(Evented, {
    /**
    * Handle an unauthorized response. Invoke this method if you are making a
    * non ember-data request that receives a 401.
    *
    * @method handleUnauthorizedResponse
    * @public
    */
    handleUnauthorizedResponse() {
    run.cancel(this._refreshTokenTimer);
    this.trigger('sessionExpired');
    },
    // ...
    });

    View Slide

  39. View Slide

  40. View Slide

  41. BEFORE STUDIO

    View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. DATA LOADING

    View Slide

  47. View Slide

  48. import Ember from 'ember';
    export default Ember.Route.extend({
    model({ search }) {
    return this.store.queryRecord('user', { search });
    }
    });

    View Slide

  49. import Ember from 'ember';
    export default Ember.Controller.extend({
    queryParams: [
    'buyingTeam',
    'category',
    'genre',
    'programmingStatus',
    'title'
    ],
    buyingTeam: null,
    category: null,
    genre: null,
    programmingStatus: null,
    title: null
    });

    View Slide

  50. import Ember from 'ember';
    export default Ember.Route.extend({
    queryParams: {
    buyingTeam: { refreshModel: true },
    category: { refreshModel: true },
    genre: { refreshModel: true },
    programmingStatus: { refreshModel: true },
    title: { refreshModel: true }
    }
    });

    View Slide

  51. import Ember from 'ember';
    const { Mixin } = Ember;
    const { assign } = Object;
    export default function makeMixin(props = {}) {
    return Mixin.create(assign({}, props));
    }
    https://ember-twiddle.com/de8c4d4aaf2b2e30f6593b671967c5a6?openFiles=utils.make-mixin.js%2C

    View Slide

  52. import Ember from 'ember';
    import makeMixin from '../utils/make-mixin';
    const mixinA = makeMixin({ value: 'a' });
    const mixinB = makeMixin({ value: 'B' });
    export default Ember.Controller.extend(mixinB, {
    appName: 'Ember Twiddle'
    });
    https://ember-twiddle.com/de8c4d4aaf2b2e30f6593b671967c5a6?openFiles=utils.make-mixin.js%2C

    View Slide

  53. import QueryParamsBuilder from './index';
    export default class EventQueryParamsBuilder extends QueryParamsBuilder {
    static TypeMap() {
    return {
    'buying_teams': {
    type: 'array',
    defaultValue: undefined,
    refreshModel: true
    },
    'title': {
    type: 'string',
    defaultValue: undefined,
    refreshModel: true
    }
    };
    }
    }

    View Slide

  54. PARACHUTE
    $ ember install ember-parachute

    View Slide

  55. View Slide

  56. View Slide

  57. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  58. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  59. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  60. View Slide

  61. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  62. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  63. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  64. ROUTE
    /
    model()
    async LOADING
    CONTROLLER
    /
    SEARCH INPUT
    search =

    View Slide

  65. View Slide

  66. import QueryParams from 'ember-parachute';
    export const myQueryParams = new QueryParams({
    search: {
    defaultValue: '',
    refresh: true
    }
    });

    View Slide

  67. export default Ember.Controller.extend(myQueryParams.Mixin, {
    queryParamsDidChange({ shouldRefresh, queryParams }) {
    if (shouldRefresh) {
    this.get('fetchUsers').perform(queryParams.search);
    }
    },
    fetchUsers: task(function*(search) {
    yield timeout(500);
    const res = yield fetch(
    `https://api.github.com/search/users?q=${search}`
    ).then(res => res.json());
    this.set('users', res.items);
    }).restartable()
    });

    View Slide

  68. ROUTE
    /
    fetch()
    CONTROLLER
    /
    SEARCH INPUT
    queryParamsDidChange
    search =

    View Slide

  69. ROUTE
    /
    fetch()
    CONTROLLER
    /
    SEARCH INPUT
    queryParamsDidChange
    search =

    View Slide

  70. ROUTE
    /
    fetch()
    CONTROLLER
    /
    SEARCH INPUT
    queryParamsDidChange
    search =

    View Slide

  71. ROUTE
    /
    fetch()
    CONTROLLER
    /
    SEARCH INPUT
    queryParamsDidChange
    search =

    View Slide

  72. https://goo.gl/rGGatD

    View Slide

  73. COMPONENT
    user-card
    TEMPLATE
    template.hbs
    TEMPLATE
    loading.hbs
    STATE
    loading

    View Slide

  74. SO MUCH DATA

    View Slide

  75. View Slide

  76. View Slide

  77. View Slide

  78. View Slide

  79. View Slide

  80. View Slide

  81. ember install @netflix/studio-data-core

    View Slide

  82. // models/some-model.js
    import SomeModel from 'studio-data-core/models/some-model';
    export default SomeModel.extend({
    // App specific logic...
    });

    View Slide

  83. const EmberApp = require('ember-cli/lib/broccoli/ember-app');
    module.exports = function(defaults) {
    let app = new EmberApp(defaults, {
    'studio-data-core': {
    importAll: true
    }
    });
    return app.toTree();
    };

    View Slide

  84. WHAT ABOUT TESTING?

    View Slide

  85. View Slide

  86. // app/models/series.js
    import DS from 'ember-data';
    const {
    Model,
    hasMany,
    belongsTo
    } = DS;
    export default Model.extend({
    statusType: belongsTo('status-type'),
    episodes: hasMany('episode')
    });
    // mirage/models/series.js
    import {
    Model,
    belongsTo,
    hasMany
    } from 'ember-cli-mirage';
    export default Model.extend({
    statusType: belongsTo(),
    episodes: hasMany()
    });
    Ember Data Models → Mirage Models

    View Slide

  87. View Slide

  88. View Slide

  89. View Slide

  90. View Slide

  91. // mirage/server.js
    // Register all models
    schema.registerModels(config.models);

    View Slide

  92. // Merge models from autogenerated Ember Data models with user defined
    models
    if (hasEmberData && config.discoverEmberDataModels) {
    let models = {};
    assign(models, getModels(), config.models || {});
    config.models = models;
    }
    // ...
    // Register all models
    schema.registerModels(config.models);

    View Slide

  93. Object.keys(requirejs.entries)
    .filter(module => !!module.match(modelMatchRegex))
    .forEach(path => {
    let paths = path.split('/');
    let modelName = paths[paths.length - 1];
    let model = require(path, null, null, true).default;
    if (isDsModel(model)) {
    DsModels[modelName] = model;
    }
    });

    View Slide

  94. Object.keys(requirejs.entries)
    .filter(module => !!module.match(modelMatchRegex))
    .forEach(path => {
    let paths = path.split('/');
    let modelName = paths[paths.length - 1];
    let model = require(path, null, null, true).default;
    if (isDsModel(model)) {
    dsModels[modelName] = model;
    }
    });

    View Slide

  95. View Slide

  96. View Slide

  97. Object.keys(getDsModels()).forEach(modelName => {
    let model = models[modelName];
    let attrs = {};
    model.eachRelationship((name, r) => {
    if (r.kind === 'belongsTo') {
    attrs[name] = belongsTo(r.type, r.options);
    } else if (r.kind === 'hasMany') {
    attrs[name] = hasMany(r.type, r.options);
    }
    });
    mirageModels[modelName] = MirageModel.extend(attrs);
    });

    View Slide

  98. Object.keys(getDsModels()).forEach(modelName => {
    let model = models[modelName];
    let attrs = {};
    model.eachRelationship((name, r) => {
    if (r.kind === 'belongsTo') {
    attrs[name] = belongsTo(r.type, r.options);
    } else if (r.kind === 'hasMany') {
    attrs[name] = hasMany(r.type, r.options);
    }
    });
    mirageModels[modelName] = MirageModel.extend(attrs);
    });

    View Slide

  99. Object.keys(getDsModels()).forEach(modelName => {
    let model = models[modelName];
    let attrs = {};
    model.eachRelationship((name, r) => {
    if (r.kind === 'belongsTo') {
    attrs[name] = belongsTo(r.type, r.options);
    } else if (r.kind === 'hasMany') {
    attrs[name] = hasMany(r.type, r.options);
    }
    });
    mirageModels[modelName] = MirageModel.extend(attrs);
    });

    View Slide

  100. View Slide

  101. DATA TABLES

    View Slide

  102. View Slide

  103. Lol jk

    View Slide

  104. SLATE

    View Slide

  105. View Slide

  106. SCROLL

    View Slide

  107. View Slide

  108. SCROLL

    View Slide

  109. View Slide

  110. View Slide

  111. View Slide

  112. View Slide

  113. EMBER-LIGHT-TABLE
    EMBER-TABLE
    HANDS-ON-TABLE
    VERTICAL-COLLECTION HORIZONTAL-COLLECTION

    View Slide

  114. EMBER-X-TABLE
    Chris Garrett
    @pzuraq
    Erik Bryn
    @ebryn
    Offir Golan
    @offirgolan
    Alex Alvarez
    @alexander-alvarez
    Jan Buschtöns
    @buschtoens

    View Slide

  115. WHAT WE ARE WORKING
    TOWARDS

    View Slide

  116. View Slide

  117. ember new my-app -b @netflix/studio-app
    DESIGN SYSTEM
    AUTHENTICATION
    METRICS LOGGING
    DEPLOYMENT
    CI / CD

    View Slide

  118. ENDE

    View Slide