Download - Extending Redux in the Server Side
Nacho Martín @nacmartin
AgentConf January 2018
Extending Redux in the Server Side
Nacho Martín (Spanish parents do actually pick this name for their kids)
I write code at Limenius.
We build tailor-made projects, and provide consultancy and formation.
We organize React Alicante.
And we are very happy with React and React Native.
http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/
http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/
Redux: Three principles
The state of the whole application is stored in an object tree within a single store.
The only way to change the state is to emit an action, an object describing what happened.
To specify how the state tree is transformed by actions, you write pure reducers.
Redux as async reduce
state = actions.reduce(reducer, initialState)
newState = reducer(action, state)
A particular way of thinking.
Useful to model certain problems.
What are we going to talk about
PLAN
TERRITORY&
Typical case
Create
Read
Update
Delete
If my DB has…
Typical case
Create
Read
Update
Delete
If my DB has… …and my REST API has…
POST
GET
PUT
DELETE+
Typical case
Create
Read
Update
Delete
If my DB has… …and my REST API has…
POST
GET
PUT
DELETE+ =
…my project will be…
“Chat tutorial paradigm”
SENDHi there! SEND
SERVERemit(‘chat_message’, ‘hi there!’)
(Whenever there is a websocket)
“Chat tutorial paradigm”
SEND SEND
SERVERemit(‘chat_message’, ‘hi there!’)
socket.on(‘chat_message', function(msg){ io.emit(‘chat_message', msg); });
(Whenever there is a websocket)
“Chat tutorial paradigm”
SEND SEND
Hi there!
SERVERemit(‘chat_message’, ‘hi there!’)
socket.on(‘chat_message', function(msg){ io.emit(‘chat_message', msg); });
socket.on(‘chat_message', function(msg){ addMessage(msg)); });
(Whenever there is a websocket)
Typically we also want
SEND SEND
Hi there!
SERVERemit(‘chat_message’, ‘hi there!’)
socket.on(‘chat_message', function(msg){ io.emit(‘chat_message', msg); });
socket.on(‘chat_message', function(msg){ addMessage(msg)); });
store(‘hi there!’)
Multiple users manipulate the same resource.
And they need to be notified in real time of the changes.
Our particular case study
Editor for nutritionists that several users can manipulate in collaboration.
Games (cards, domino, quizzes…).
Example: Quiz Game (Multiplayer)
User picks answer 3
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
Example: Quiz Game (Multiplayer)
User picks answer 3
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: answer player: 1, answer:3, correct: false}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: answer player: 1, answer:3, correct: false}
Event
Mark answer as selected
Example: Quiz Game (Multiplayer)
User picks answer 3Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: new_question question: q}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: new_question question: q}
Event
Change question
Example: Quiz Game (Multiplayer)
User picks answer 3Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: new_scores scores: sc}
Event
Example: Quiz Game (Multiplayer)
User picks answer 3
Event’
Is the answer correct? Has everybody else answered? Should we ask a new question? Is the game over?
Mark answer as selected
SERVER CLIENTCLIENT
State State’ State
{type: answer answer:3}
{type: new_scores scores: sc}
Event
Update scores
Example: Quiz Game (Multiplayer)
Events’
SERVER CLIENT
State State’Events
Two vocabularies of eventsTwo representations of the stateTwo ways of calculating the state
Example: Quiz Game (Multiplayer)
Events’
SERVER CLIENT
State State’Events
Two applications :_(
Best situation
User disconnected! 💀 SERVERCLIENT
State State’🎉
Best situation
User reconnected! SERVERCLIENT
State State’Plz give me the state
Best situation
SERVERCLIENT
State State’{get_state: state}
🎉
by Rob Potter
SERVER
State
CLIENT
State
CLIENT
State
SERVER
State
CLIENT
State
CLIENT
State
Event
SERVER
State
CLIENT
State
CLIENT
State
Event
Update state
SERVER
State
CLIENT
State
CLIENT
State
Event
State
Update state
State
Making the backend play well with Redux
A part of the client’s Redux state lives in the server.
The client changes it sending events to the server.
The server pushes (slices of) the state to the clients.
Let’s build the simplest example
With Redux directly in node.js
Views
Actions
Reducers
StateStore
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
<Grid> {board.map((row, idxRow) => ( <Row idx={idxRow} key={idxRow}> {row.map((tile, idx) => ( <Tile onClick={() => this.socket.emit("game:play", { x: idx, y: idxRow })} key={idx} idx={idx} tile={tile} /> ))} </Row> ))} </Grid>
Client
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
io.on("connection", function(socket) { //… socket.on("game:play", function(data) { store.dispatch({ type: GAME_PLAY, x: data.x, y: data.y }); }); socket.on("game:reset", function(data) { store.dispatch({ type: GAME_RESET }); });});
Server
const reducer = function(state = initialState, action = {}) { switch (action.type) { case GAME_PLAY: const { stateAfterPlayer, waitingForOpponent } = makePlayerMove(state, { x: action.x, y: action.y }); if (!waitingForOpponent) { return stateAfterPlayer; } return makeAIMove(stateAfterPlayer); case GAME_RESET: return initialState; default: return state; }};
Server
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
observeStore( store, state => state, state => { socket.emit("game_update", state) } );
function observeStore(store, select, onChange) { let currentState;
function handleChange() { let nextState = select(store.getState()); if (nextState !== currentState) { currentState = nextState; onChange(currentState); } }
let unsubscribe = store.subscribe(handleChange); handleChange(); return unsubscribe;}
Server
componentDidMount() { const { dispatch } = this.props; let socket = ioClient("http://localhost:3080"); socket.on("game_update", gameState => dispatch(updateGame(gameState))); }
Client
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
export default function reducer(state = initialState, action = {}) { switch (action.type) { case GAME_UPDATE: return { ...state, board: action.game.board, phase: action.game.phase };
default: return state; }}
export function updateGame(game) { return { type: GAME_UPDATE, game };}
Client
Views
Actions
Reducers
StateStore
Events
Store
State
Reducers
Client Server
const mapStateToProps = state => ({ board: state.board, phase: state.phase });
export default connect(mapStateToProps)(Board);
Client
With Elixir OTP
Used in
Databases, gaming, telecom, message passing…
“Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability”
😍OPEN TELECOM PLATFORM😍
Supervisor
GenServerGenServerSupervisor
GenServerGenServer GenServer
OTP Supervision trees
Gen Server callbacks
defmodule Tictactoe.GameServer do use GenServer
#Callbacks def init(args) do {:ok, state} end
def handle_cast(request, state) do {:noreply, new_state} endend
state = actions.reduce(reducer, initialState)
newState = reducer(action, state);
Views
Actions
Reducers
StateStore
Events
GenServer
State
Callbacks
Client Server
defmodule TictactoeWeb.GameChannel do use Phoenix.Channel
def join("game", _message, socket) do game = Tictactoe.GameServer.getGameState() {:ok, game, socket} end
def handle_in("game:play", %{"x" => x, "y" => y}, socket) do Tictactoe.GameServer.play(x, y) {:noreply, socket} end
def handle_in("game:reset", _, socket) do Tictactoe.GameServer.reset() {:noreply, socket} endend
def init(:ok) do {:ok, %State{}} end
const reducer = function(state = initialState, action = {}) { switch (action.type) { //… default: return state; }};
const reducer = function(state, action) { switch (action.type) { //.. case GAME_RESET: return initialState; //… }};
def handle_cast(%{:action => :reset}, _state) do state = %State{} Endpoint.broadcast(“game", “game_update", serialize_game(state)) {:noreply, state} end
def handle_cast(%{:action => :play, :x => x, :y => y}, state) do case can_move(state, x, y) do :true -> state1 = put_in state.board[y][x], "X" state2 = state |> check_finished |> make_random_move |> check_finished Endpoint.broadcast( “game", “game_update", serialize_game(state2)) state2 :false -> state end {:noreply, state} end
const reducer = function(state, action) { switch (action.type) { case GAME_PLAY: const { stateAfterPlayer, waitingForOpponent } = makePlayerMove(state, { x: action.x, y: action.y }); if (!waitingForOpponent) { return stateAfterPlayer; } return makeAIMove(stateAf); //.. }};
Demo
https://github.com/Limenius/tictactoe
CAN WE THINK ABOUT THIS
AS AN IN-MEMORY DATABASE? 🤔
by Sindre Aalberg
And can we think about this as Event Sourcing?
CLIENT
State
SERVER
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVER
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVERLog
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVER
View
Log
Queries
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVER
View
Log
Queries
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVER
View
Log
Another view
Queries
And can we think about this as Event Sourcing?
CLIENT
State
Events
SERVERLog
Another view
View (State)
And can we think about this as Event Sourcing?
CLIENT
State
Events
Subscribe
SERVERLog
Another view
View (State)