full stack toronto - the 3r stack

Post on 26-Jan-2017

330 Views

Category:

Internet

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

The 3R Stack

Scott Persinger

Senior Director, Heroku

scottp@heroku.com@persingerscott

Meet the team

Howard Burrows Jeremy West Scott Persinger

3R Stack

Real-time, RESTful apps with React

Traditional Web Apps

BROWSERServer Generated

HTML

HTTP Request / Response

how did we get here?

Remember the good ol’ days?

<?php $db = mysql_connect("localhost", "root"); mysql_select_db("mydb",$db); $result = $mysqli->query("SELECT title FROM City LIMIT 1"); $title = $result->rows[0].title;?><html> <head> <title><?php echo $title ?></title> </head> <body> <center> <h2><?php echo $title ?></h2> </center> </body></html>

Dynamic Web Apps

BROWSER

DOM updates

Server Generated HTML

REST API

HTTP Request / Response

how did we get here?

AJAX

Single-Page Apps (“rich client”)

Javascript App

REST API

new state

how did we get here?

AJAX

AJAX check for updates

Why real-time?

● Collaboration apps○ See changes made by collaborators

immediately○ Avoid change conflicts

● Real-time monitoring○ See changing status immediately

● Eliminate polling○ Incurs a heavy system load

Real-time Apps

Javascript App

REST API

new state

how did we get here?

AJAX

Websocket PUSH

Websocket “SUBSCRIBE”

Rich-client Architecture

Business logic and

persistent state

RE

ST

AP

I

Rich Web client

Mobile client

Partner apps

Application architecture

Redis

server

Express

Mongoose

Node.js

socket.io

save hooks

Mongo DB

browser

React

Redux

socket.iofetch

REST API

Websocket

Server side code tour

Swagger.io

var express = require('express');

var app = express();

var router = express.Router();

router.route('/users')

.get(function(req, res) {

User.find({name: {$ne: 'admin'}}, function(err, users) {

res.json(users.map(function(q) {return q.toObject()}));

});

})

.post(function(req, res) {

var user = new User(req.body);

user.save(function(err) {

res.status(201).json(user.toObject({virtuals:true}));

});

});

app.use('/api', router);

index.js

var request = require('supertest')

request = request(app);

describe("Users CRUD...", function() {

it('can POST a new user', function(done) {

request

.post('/api/users')

.send({name:"admin"})

.expect(200)

.end(function(err, res) {

res.body.token.should.be.ok;

models.User.count({name:"admin"}, function(err, c) {

c.should.equal(1);

done();

});

});

});

});

test/users.js

browser

Reactcomponents

Front-end architecture

REST APIReact

components

Reactcomponents Action creators

“dispatch”

Actions

Store reducers

user actions

The Flux architecture

● How do I manage state in my app?● One way flow:

Action creators -> store -> components -> DOM● Encapsulate state changes as “actions”

const GameTabs = React.createClass({

componentWillMount() {

this.props.dispatch(list_users());

},

});

export function list_users() {

return function(dispatch) {

fetch(API_BASE_URL + 'api/users')

.then(response => response.json())

.then(json => {

dispatch({

type: USERS_LIST,

payload: json,

});

})

};

}

JSX component

Action creator action

components/game_tabs.jsx actions/users.js

function orderByPoints(users) {

return _.sortBy(users, user => -user.points);

}

export default function users(state=[], action) {

switch (action.type) {

case USERS_LIST:

return orderByPoints(action.payload);

case USERS_CREATE:

return orderByPoints([...state, action.payload]);

});

reducers/users.list

function mapStateToProps(state) {

return {

users: state.users,

game: state.game,

guesses: state.guesses,

};

}

const GameTabs = React.createClass({

render() {

return (

<Tab label="Leaderboard" >

<LeaderboardTable users=

{this.props.users} /> : ''}

</Tab>

);

}

});

game_tabs.jsx

export default React.createClass({

render: function () {

return (

<Table selectable={false}>

{this.props.users.map(user =>

<TableRow key={user.name}>

<TableRowColumn>

{user.name}

</TableRowColumn>

<TableRowColumn>

{user.points || 0}

</TableRowColumn>

</TableRow>

)}

</Table>

);

},

});

leaderboard.jsx

The reducer pattern

● Reducers interpret actions to change the application state

● Just a big switch statement● Actions are like the command pattern● Encapsulate state changes in a single place● The store is the state

Now let’s add real-time...

Key insight - add “pub/sub” to REST

Create - POSTRead - GETUpdate - PATCHDelete - DELETE

Listen - SUBSCRIBE [event]Event - PUBLISH

HTTP

Websocket

io.on('connection', function(socket) {

socket.on('subscribe', function(resource) {

// Authorize client and record subscription

});

});

UserSchema.post('save', function(doc) {

model_signals(doc.wasNew ? 'create' : 'update', ‘User’, doc);

});

function model_signals(action, modelName, doc) {

// Dispatch model event to WS subscribers

io.emit('publish',

{resource: modelName, action: action, data: doc.toObject()});

}

WS connect

model signal

WS publish

In a real application we would publish to Redis first, so all Node processes could publish.

browser

Reactcomponents

Front-end architecture

REST API

Websocket

Reactcomponents

Reactcomponents Action creators

“dispatch”

Actions

Store

api-events

reducers

event-router

let socket = this.socket = io(API_BASE_URL, {transports:['websocket']});

socket.on('publish', this._handleNotify.bind(this));

_handleNotify(data) {

store.dispatch(userAPIUpdate(data));

}

export function userAPIUpdate(event) {

switch(event.action) {

case 'create':

return {type: USERS_CREATE, payload: event.data};

export default function users(state=[], action) {

switch (action.type) {

case USERS_CREATE:

return [...state, action.payload];

}

}

api-events

reducers/ users

actions/ users

new user inserted

Real-time REST

● All resource changes are published over the real-time channel to interested subscribers

● Subscriptions are modeled with the resource path, like:○ SUBSCRIBE /users - listen to changes to all users○ SUBSCRIBE /users/1 - list to changes to user 1

● Simplifies design of the event channel○ Eliminates “ad-hoc message” pattern

the quiz!

https://quizlive-react.herokuapp.com

http://tinyurl.com/quizyyz

Take-aways

● Thick client + REST API is a very nice separation of concerns, but more work than MVC

● React/Flux/Redux stack is quite complicated, especially JSX

● Real-time isn’t free○ Puts query/serialization load on the backend

● But it’s a great user experience!

thank you

@persingerscott

top related