full stack toronto - the 3r stack

30
The 3R Stack Scott Persinger Senior Director, Heroku [email protected] @persingerscott

Upload: scott-persinger

Post on 26-Jan-2017

334 views

Category:

Internet


3 download

TRANSCRIPT

Page 1: Full Stack Toronto - the 3R Stack

The 3R Stack

Scott Persinger

Senior Director, Heroku

[email protected]@persingerscott

Page 2: Full Stack Toronto - the 3R Stack

Meet the team

Howard Burrows Jeremy West Scott Persinger

Page 3: Full Stack Toronto - the 3R Stack

3R Stack

Real-time, RESTful apps with React

Page 4: Full Stack Toronto - the 3R Stack

Traditional Web Apps

BROWSERServer Generated

HTML

HTTP Request / Response

how did we get here?

Page 5: Full Stack Toronto - the 3R Stack

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>

Page 6: Full Stack Toronto - the 3R Stack

Dynamic Web Apps

BROWSER

DOM updates

Server Generated HTML

REST API

HTTP Request / Response

how did we get here?

AJAX

Page 7: Full Stack Toronto - the 3R Stack

Single-Page Apps (“rich client”)

Javascript App

REST API

new state

how did we get here?

AJAX

AJAX check for updates

Page 8: Full Stack Toronto - the 3R Stack

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

Page 9: Full Stack Toronto - the 3R Stack

Real-time Apps

Javascript App

REST API

new state

how did we get here?

AJAX

Websocket PUSH

Websocket “SUBSCRIBE”

Page 10: Full Stack Toronto - the 3R Stack

Rich-client Architecture

Business logic and

persistent state

RE

ST

AP

I

Rich Web client

Mobile client

Partner apps

Page 11: Full Stack Toronto - the 3R Stack

Application architecture

Redis

server

Express

Mongoose

Node.js

socket.io

save hooks

Mongo DB

browser

React

Redux

socket.iofetch

REST API

Websocket

Page 12: Full Stack Toronto - the 3R Stack

Server side code tour

Page 13: Full Stack Toronto - the 3R Stack

Swagger.io

Page 14: Full Stack Toronto - the 3R Stack

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

Page 15: Full Stack Toronto - the 3R Stack

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

Page 16: Full Stack Toronto - the 3R Stack

browser

Reactcomponents

Front-end architecture

REST APIReact

components

Reactcomponents Action creators

“dispatch”

Actions

Store reducers

user actions

Page 17: Full Stack Toronto - the 3R Stack

The Flux architecture

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

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

Page 18: Full Stack Toronto - the 3R Stack

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

Page 19: Full Stack Toronto - the 3R Stack

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

Page 20: Full Stack Toronto - the 3R Stack

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

Page 21: Full Stack Toronto - the 3R Stack

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

Page 22: Full Stack Toronto - the 3R Stack

Now let’s add real-time...

Page 23: Full Stack Toronto - the 3R Stack

Key insight - add “pub/sub” to REST

Create - POSTRead - GETUpdate - PATCHDelete - DELETE

Listen - SUBSCRIBE [event]Event - PUBLISH

HTTP

Websocket

Page 24: Full Stack Toronto - the 3R Stack

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.

Page 25: Full Stack Toronto - the 3R Stack

browser

Reactcomponents

Front-end architecture

REST API

Websocket

Reactcomponents

Reactcomponents Action creators

“dispatch”

Actions

Store

api-events

reducers

event-router

Page 26: Full Stack Toronto - the 3R Stack

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

Page 27: Full Stack Toronto - the 3R Stack

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

Page 28: Full Stack Toronto - the 3R Stack

the quiz!

https://quizlive-react.herokuapp.com

http://tinyurl.com/quizyyz

Page 29: Full Stack Toronto - the 3R Stack

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!

Page 30: Full Stack Toronto - the 3R Stack

thank you

@persingerscott