A guide to predictable scalability
Yazan Alaboudi
@YazanAlaboudi
When a user successfully subscribes to a policy:
Policy Service
HTTP
{ ... }
Billing Service
API call
When a user successfully subscribes to a policy:
Policy Service
HTTP
{ ... }
Billing Service
Email Service
API call
API call
!
requires change & redeployment
!
When a user successfully subscribes to a policy:
Policy Service
HTTP
{ ... }
Billing Service
Email Service
Parcel Service
API call
API call
API call
!
requires change & redeployment
!
When a user successfully subscribes to a policy:
.... But as MORE requirements come in
Policy Service
HTTP
{ ... }
Billing Service
Email Service
Parcel Service
API call
API call
API call
!
requires change & redeployment
!
API call
API call
API call
🔥 crazy stuff is going on here
🔥
Policy Service
Message
Bus
HTTP
{ ... }
dispatch
"Policy Purchased"
Billing Service
Email Service
Parcel Service
subscribed to
"Policy Purchased"
subscribed to
"Policy Purchased"
subscribed to
"Policy Purchased"
Publisher reports an event
Subscriber is interested in an event
Message Bus facilitates this communication
Policy Service
Message
Bus
HTTP
{ ... }
dispatch
"Policy Purchased"
Billing Service
Email Service
Parcel Service
subscribed to
"Policy Purchased"
subscribed to
"Policy Purchased"
subscribed to
"Policy Purchased"
publisher
bus
subscribers
1. Publishers should be unaware of their subscribers
2. Subscribers should be unaware of publishers
dispatcher
redux
Actor
Action
reducer B
reducer A
reducer C
publisher
bus
subscribers
Action
Action
Action
Policy Service
Message
Bus
HTTP
{ ... }
dispatch
"start billing"
Billing Service
Email Service
Parcel Service
subscribed to
"send confirmation"
subscribed to
"start billing"
subscribed to
"send letter"
dispatch
"send letter"
dispatch
"send confirmation"
!
requires change & redeployment
🔥 crazy stuff is going on here
!
🔥
1. Publishers should be unaware of their subscribers
2. Subscribers should be unaware of publishers
When a player scores a goal:
// in scoreboard reducer.js
const INITIAL_STATE = {
home: 0,
away: 0
};
function scoreboardReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "INCREMENT_SCORE": {
const scoringSide = action.payload;
return { ...state, [scoringSide]: state[scoringSide] + 1};
}
default: return state;
}
}
When a player scores a goal:
// in scoreboardReducer.js
const INITIAL_STATE = {
home: 0,
away: 0
};
function scoreboardReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "INCREMENT_SCORE": {
const scoringSide = action.payload;
return { ...state, [scoringSide]: state[scoringSide] + 1};
}
default: return state;
}
}
//in crowdExcitmentReducer.js
const INITIAL_STATE = 0;
function crowdExcitementReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "INCREASE_CROWD_EXCITEMENT": return state + 1;
default: return state;
}
}
// in GameComponent
class GameComponent extends React.Component {
scoreGoal() {
dispatch({ type: "INCREMENT_SCORE", scoringSide: "home"});
dispatch({ type: "INCREASE_CROWD_EXCITEMENT"});
// potentially more dispatches
}
render() {
//...
}
}
// in scoreboardReducer.js
const INITIAL_STATE = {
home: 0,
away: 0
};
function scoreboardReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "GOAL_SCORED": {
const scoringSide = action.payload;
return { ...state, [scoringSide]: state[scoringSide] + 1};
}
default: return state;
}
}
//in crowdExcitmentReducer.js
const INITIAL_STATE = 0;
function crowdExcitementReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "GOAL_SCORED": return state + 1;
default: return state;
}
}
// in GameComponent
class GameComponent extends React.Component {
scoreGoal() {
dispatch({ type: "GOAL_SCORED", scoringSide: "home"});
}
render() {
//...
}
}
// in scoreboardReducer.js
const INITIAL_STATE = {
home: 0,
away: 0
};
function scoreboardReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "GOAL_SCORED": {
const scoringSide = action.payload;
return { ...state, [scoringSide]: state[scoringSide] + 1};
}
default: return state;
}
}
//in crowdExcitmentReducer.js
const INITIAL_STATE = 0;
function crowdExcitementReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case "GOAL_SCORED": return state + 1;
default: return state;
}
}
// in GameComponent
class GameComponent extends React.Component {
scoreGoal() {
dispatch({ type: "INCREMENT_SCORE", scoringSide: "home"});
dispatch({ type: "INCREASE_CROWD_EXCITEMENT"});
// potentially more dispatches
}
render() {
//...
}
}
// in GameComponent
class GameComponent extends React.Component {
scoreGoal() {
dispatch({ type: "GOAL_SCORED", scoringSide: "home"});
}
render() {
//...
}
}
Actor | Side Effect | Code Location |
---|---|---|
User | State | Reducer |
User | Not State (scheduling, http, storage, etc) | Custom Side Effect Handler |
Not the User (time, server, etc) | State | Custom Side Effect Handler & Reducer |
Not the User (time, server, etc) | Not State (scheduling, http, storage, etc) | Custom Side Effect Handler |