building real time app
TRANSCRIPT
BUILDING REAL-TIME APP
By Steven Yap Futureworkz Pte. Ltd.
Steven [email protected]://github.com/stevenyap
• Host Saigon.rb Ruby Meetup• Co-Founder of Futureworkz• Ruby on Rails coder• Agile startup consultant
Awesome Ruby on Rails Developmenthttp://www.futureworkz.com
http://playbook.futureworkz.com/
WHAT IS A REAL-TIME APP?
“the real-time web consists in making the client interface (or the web side; or the web layer) of a web application, to communicate continuously with the corresponding real-time server, during every user connection.
wikipedia.org/wiki/Real-time_web
USERS DON'T NEED TO REFRESH THE APP TO
GET NEW DATA
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind
A husband and wife shares the same to-do list
Wife creates a to-do item "Buy fruits"
Husband sees the new item (without refreshing!)
Husband sees the total number of items of the list is now 1Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list
Wife creates a to-do item "Buy fruits"
Husband sees the new item (without refreshing!)
Husband sees the total number of items of the list is now 1Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits"
Husband sees the new item (without refreshing!)
Husband sees the total number of items of the list is now 1Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!)
Husband sees the total number of items of the list is now 1Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1
Find husband, broadcast count data and update view
Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1
Find husband, broadcast count data and update view
Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"
API update item and create item
Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1
Find husband, broadcast count data and update view
Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"
API update item and create item
Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Find wife, broadcast changes and update the view
Wife sees the total number of items is now 2
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1
Find husband, broadcast count data and update view
Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"
API update item and create item
Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Find wife, broadcast changes and update the view
Wife sees the total number of items is now 2 Broadcast to wife and update the count
Wife deletes "Buy oranges"
HOW DO YOU?
Use Cases Development Effort
Build a real time To-do list app in your mind Setup Rails
A husband and wife shares the same to-do list Setup WebSocket
Wife creates a to-do item "Buy fruits" API create item
Husband sees the new item (without refreshing!) Find husband, broadcast item data and update view
Husband sees the total number of items of the list is now 1
Find husband, broadcast count data and update view
Husband refactor the item from "Buy fruits"to "Buy apples" and "Buy oranges"
API update item and create item
Wife sees the "Buy fruits" is changed to "Buy apples" and sees a new item "Buy oranges"
Find wife, broadcast changes and update the view
Wife sees the total number of items is now 2 Broadcast to wife and update the count
Wife deletes "Buy oranges" API delete item, find husband, broadcast item data, broadcast count data and update the view
DIFFICULTIES IN BUILDING REAL TIME APP
➤ Managing a constant connection between client and server (Long polling or WebSocket)
➤ Messy code to update view via DOM manipulation (prepend, append, change HTML content, remove element)
➤ Messy code to push new changes to the correct clients
➤ Finding the correct clients and WebSocket channels
➤ Detect changes in item count
➤ If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update.
➤ Total number of items in a list
➤ Total number of items by husband
➤ Total number of items by wife
➤ New item created by mistress in a shared list with husband
A PROPOSED SOLUTION - R4
➤ Rails 5 ActionCable
➤ RethinkDB
➤ ReactJS
➤ Redux
RAILS 5 - ACTIONCABLE
RAILS 5 - ACTIONCABLE
➤ WebSocket is a bi-directional real-time communication between client and server
➤ It is a persistent connection between server and browser
➤ Server can send data to browser
➤ Browser can send data back to server
➤ Rails 5 ActionCable makes it "easy" to setup WebSocket
RAILS 5 - ACTIONCABLE
$ gem install rails --pre --no-ri --no-rdoc --api
RAILS 5 - ACTIONCABLE
$ gem install rails --pre --no-ri --no-rdoc --api
#app/channels/todos_channel.rbclassTodosChannel<ApplicationCable::Channeldefsubscribedstream_from'todo'endend
RAILS 5 - ACTIONCABLE
$ gem install rails --pre --no-ri --no-rdoc --api
#app/channels/todos_channel.rbclassTodosChannel<ApplicationCable::Channeldefsubscribedstream_from'todo'endend
#SendamessagetoclientActionCable.server.broadcast('todo',{task:'BuyApples'})
RAILS 5 - ACTIONCABLE
//JavascriptApp.cable.subscriptions.create('TodosChannel',{received:(data)=>{console.log(data)}})
RAILS 5 - ACTIONCABLE
# app/channels/todos_channel.rb classTodosChannel<ApplicationCable::Channeldefsubscribedstream_from'todo'end
defcreate(data)todo=Todo.new(data)
iftodo.saveActionCable.server.broadcast('todo',todo)elseActionCable.server.broadcast('todo','Savefailed')endendend
RAILS 5 - ACTIONCABLE
//JavascriptApp.cable.subscriptions.create('TodosChannel',{received:(data)=>{console.log(data)},
onSubmit:(task)=>{//senddatabacktoserver//callsTodosChannel#create//withdata={task:task}this.perform('create',{task:task})}})
RAILS 5 - ACTIONCABLE
➤ Two kinds of broadcast:
➤ Operation broadcast (CRUD)
➤ Notification broadcast
➤ Operation broadcast is easy - simply broadcast back the result
➤ Notification broadcast is messy
➤ A todo item created
➤ Broadcast to wife/husband - which channel? which stream?
➤ Broadcast the new count - which channel? which stream?
➤ Broadcast to others - which channel? which stream?
➤ Code found in Channel/Controller/Model/Job/etc
RETHINKDB
RETHINKDB
➤ Open Source NoSQL database
➤ Stream changes to server for any changes in data
➤ Instead of finding all the channels to broadcast to, we simply put all notification broadcasting in our channel#subscribed
RETHINKDB SETUP
#Gemfilegem'nobrainer'
$railsgnobrainer:install
RETHINKDB SETUP
#models/todo.rbclassTodoincludeNoBrainer::DocumentincludeNoBrainer::Document::Timestamps
field:task,type:String,required:truefield:completed,type:Boolean,required:trueend
RETHINKDB SETUP
#models/todo.rbclassTodoincludeNoBrainer::DocumentincludeNoBrainer::Document::Timestamps
field:task,type:String,required:truefield:completed,type:Boolean,required:trueend
#UseitlikeActiveRecord!(mostly)todo=Todo.new({task:'Buyapples',completed:false})todo.savetodo.errors.full_messages
SUBSCRIBE TO CHANGES
#app/channels/todos_channel.rbclassTodosChannel<ApplicationCable::Channeldefsubscribedstream_from'todo'
Todo.all.raw.changes.eachdo|changes|ActionCable.server.broadcast(@stream,changes)endend#...end
CHANGES STREAM#createatodo{"new_val"=>{"id"=>"3SGzWZLEdC2D6P","task"=>"buyoranges","completed"=>false,"created_at"=>2016-06-0707:31:47+0000,"updated_at"=>2016-06-0707:31:47+0000},"old_val"=>nil}
CHANGES STREAM#updateatodo{"new_val"=>{"id"=>"3SEIgBkmjHNuNy","task"=>"buyoranges","completed"=>false,"created_at"=>2016-06-0703:12:40+0000,"updated_at"=>2016-06-0707:32:40+0000},"old_val"=>{"id"=>"3SEIgBkmjHNuNy","task"=>"buyapples","completed"=>false,"created_at"=>2016-06-0703:12:40+0000,"updated_at"=>2016-06-0707:32:24+0000}}
CHANGES STREAM#destroyatodo{"new_val"=>nil,"old_val"=>{"id"=>"3SEIgBkmjHNuNy","task"=>"buyoranges","completed"=>false,"created_at"=>2016-06-0703:12:40+0000,"updated_at"=>2016-06-0707:32:40+0000}}
RETHINKDB
➤ Notification broadcasting only occurs in channel subscription from RethinkDB
➤ Not hidden in Active Record, Active Job, controllers, channel subscriptions, etc
➤ Single source of notification broadcast
REACTJS
REACTJS
➤ Given a set of data, it will render
➤ When the set of data changes, it will automatically re-render
➤ Declarative style, easy to see how the view will turn out
➤ Easy to reason how data is transformed into view
REACTJS WALKTHROUGH
REACTJS
➤ Given a fixed data, ReactJS will always render correctly
➤ We can make ReactJS deal with rendering only
➤ Can we extract all data/state management to another place?
REDUX
REDUX
➤ Gives the state of entire frontend in a JS Object
➤ When the data changes, it creates a new state and pass to all the React components to re-render (if necessary)
➤ Extracts away all data logic from React
➤ React renders purely based on the state from Redux
➤ Single source of truth for React
REDUX
#InitialStatestate={todos:[]}
#ReactRendersnothing
REDUX
#NewMessagetoreducers{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false}
REDUX
#NewMessagetoreducers{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false}]}
REDUX#NewMessagetoreducers{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false}]}
#ReactRenders<p>buyapple</p>
REDUX
#NewMessagetoreducers{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false}
REDUX#NewMessagetoreducers{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false},{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false}]}
REDUX#NewMessagetoreducers{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false},{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false}]}
#ReactRenders<p>buyapple</p><p>buyoranges</p>
REDUX
#NewMessagetoreducers{id:"3SxIYBkmjUnxms",task:"buypears",completed:false}
REDUX#NewMessagetoreducers{id:"3SxIYBkmjUnxms",task:"buypears",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false},{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false},{id:"3SxIYBkmjUnxms",task:"buypears",completed:false}]}
REDUX#NewMessagetoreducers{id:"3SxIYBkmjUnxms",task:"buypears",completed:false}
#NewStatestate={todos:[{id:"3SEIgBkmjHNuNy",task:"buyapple",completed:false},{id:"3SGzWZLEdC2D6P",task:"buyoranges",completed:false},{id:"3SxIYBkmjUnxms",task:"buypears",completed:false}]}
#ReactRenders<p>buyapple</p><p>buyoranges</p><p>buypears</p>
CONNECTING REDUX & REACTJS
WIRING THEM UP
➤ ReactJS is (almost) pure functions of view
➤ Redux is the single source of truth for frontend data
➤ Rails provides the business logic and WebSocket connection
➤ RethinkDB is the single source of broadcast
RethinkDB
Rails
Redux
ReactJS
dispatch
perform
broadcast
BENEFITS
➤ Separation of concerns
➤ Easier to subscribe to multiple data changes
➤ Increased use of WebSocket
➤ Real-time app made easy!
FOOD FOR THOUGHT
➤ Using Opal for frontend React/Redux
➤ Pure frontend vs monolithic app
➤ Redux + React Native mobile developmentwith Rails + Rethinkdb backend
➤ Storing Redux state in backend vs frontend
➤ Websocket vs HTTP
USING OPAL FOR FRONTEND REACT/REDUX
➤ Ruby everywhere!
➤ Isomorphic app - share code between frontend and backend
PURE FRONTEND (REDUX + REACTJS) VS MONOLITHIC APP
➤ Enjoy the goodness of frontend development
➤ Separation of concern between frontend and backend
REDUX + REACT NATIVE MOBILE DEVELOPMENT WITH RAILS + RETHINKDB BACKEND
➤ React = "Learn once, write every where"
➤ ReactJS is very similar to React Native
➤ Easy to onboard web developers onto mobile developments
➤ Enjoy hot reloading, code push, etc
STORING REDUX STATE IN BACKEND VS FRONTEND
➤ Persist frontend state in backend
➤ Write reducers in Ruby!
➤ Faster performance in calculating new state
➤ Stream state diff to frontend
WEBSOCKET VS HTTP API
➤ WebSocket is bi-directional
➤ Why do we need to call HTTP API?
➤ Less overhead, faster response
➤ HTTP - fetch and wait and response
➤ WebSocket - push to server and react on response
RESOURCES
➤ https://github.com/rails/rails/tree/master/actioncable
➤ https://www.rethinkdb.com/docs/guide/ruby/
➤ https://facebook.github.io/react/docs/thinking-in-react.html
➤ http://redux.js.org/index.html
➤ https://egghead.io/courses/getting-started-with-redux