DATABASE EVERYWHERE

 

A Reactive Data Architecture

for JavaScript Frontends

Evan You

尤雨溪

Core dev @ Meteor

微博 @尤小右 / 知乎 @尤雨溪

Twitter @youyuxi / GitHub @yyx990803

What makes it hard to build a modern web app?

Keeping state in sync.

UI State

Client Data State

Server Data State

Everything can go wrong when you manage it imperatively.

UI State

Client Data State

We've gotten pretty good at dealing with this...

Client Data State

Server Data State

But how about this?

REST

Model Abstractions over REST

Flux Stores ?

Problems with the

client-model-over-REST abstraction

Trouble with non-conventional APIs

Difficulty managing optimistic updates

Pull-centric design leads to imperative code

What can we do?

Let the UI talk to the database!

UI State

Server Data State

Cutting-edge Developments in Frontend Data-fetching

Relay / GraphQL

Falcor / JSON Graph

Om Next

Unfortunately, none of these are ready yet...

METEOR

DDP

Distributed Data Protocol

"REST for websockets"

Tracker

Fullstack Reactivity

var light = new ReactiveVar('green');

Tracker.autorun(function() {
  console.log('The light is', light.get());
});

light.set('amber');
light.set('red');

// Result:

> 'The light is green'
> 'The light is amber'
> 'The light is red'

Minimongo

Isomorphic Database Driver

UI State

Minimongo

Client Data State & Server Data State

Tasks = new Mongo.Collection("tasks");

if (Meteor.isClient) {
  Template.body.helpers({
    
    // reactive helper function
    // just return what you need
    tasks: function () {
      return Tasks.find({});
    }

  });
}
<ul>
  {{#each tasks}}
    <li>{{text}}</li>
  {{/each}}
</ul>
// define a data updating method on server
Meteor.methods({
  addTask: function (text) {
    // Make sure the user is logged in before inserting a task
    if (! Meteor.userId()) {
      throw new Meteor.Error("not-authorized");
    }

    Tasks.insert({
      text: text,
      createdAt: new Date(),
      owner: Meteor.userId(),
      username: Meteor.user().username
    });
  }
});
// calling it from client, as easy as:
Meteor.call("addTask", text);

Optimistic UI updates

Publish & Subscribe

Component-level Subscription

Template.notifications.onCreated(function () {
  // Use this.subscribe inside onCreated callback
  this.subscribe("notifications")
})
<template name="notifications">
  {{#if Template.subscriptionsReady}}
    <!-- This is displayed when all data is ready. -->
    {{#each notifications}}
      {{> notification}}
    {{/each}}
  {{else}}
    Loading...
  {{/if}}
</template>

Did I mention realtime?

This is nice,

but what if I want to use another frontend framework?

meteor add urigo:angular
meteor add react
angular.module("simple-todos", ['angular-meteor']);

angular.module("simple-todos")
  .controller("TodosListCtrl", ['$scope',
    function($scope){
      $scope.tasks = [...];
    }
  ]);
<ul ng-repeat="task in tasks">
  <li>{{task.text}}</li>
</ul>
Tasks = new Mongo.Collection("tasks");

if (Meteor.isClient) {

  angular.module("simple-todos", ['angular-meteor']);

  angular.module("simple-todos")
    .controller("TodosListCtrl", ['$scope', '$meteor',
      function($scope, $meteor){
        $scope.tasks = $meteor.collection(Tasks);
      }
    ]);
}
<ul ng-repeat="task in tasks">
  <li>{{task.text}}</li>
</ul>

+

var App = React.createClass({
  getInitialState() {
    return {
      tasks: [...]
    };
  },
  render() {
    return (
      <ul>
        {this.state.tasks.map(function (task) {
          return <li key={task._id}>{task.content}</li>;
        })}
      </ul>
    );
  }
});
var Tasks = new Mongo.Collection("tasks");

var App = React.createClass({
  mixins: [ReactMeteorData],
  getMeteorData() {
    // This method knows how to listen to Meteor's
    // reactive data sources, such as collection queries
    return {
      // Return an array with all items in the collection
      tasks: Tasks.find().fetch()
    };
  },
  render() {
    return (
      <ul>
        {this.data.tasks.map(function (task) {
          return <li key={task._id}>{task.content}</li>;
        })}
      </ul>
    );
  }
});

+

Thanks!

Made with Slides.com