BEYOND FLUX BEYOND FLUX SCALABLE FRONTEND ARCHITECTURES SCALABLE - - PowerPoint PPT Presentation

beyond flux beyond flux
SMART_READER_LITE
LIVE PREVIEW

BEYOND FLUX BEYOND FLUX SCALABLE FRONTEND ARCHITECTURES SCALABLE - - PowerPoint PPT Presentation

BEYOND FLUX BEYOND FLUX SCALABLE FRONTEND ARCHITECTURES SCALABLE FRONTEND ARCHITECTURES USING PUBLISH/SUBSCRIBE USING PUBLISH/SUBSCRIBE Michael Kurze @ goto Amsterdam 2016 1 WHY ARCHITECTURE? WHY ARCHITECTURE? IT'S ABOUT TIME! IT'S ABOUT


slide-1
SLIDE 1

1

BEYOND FLUX BEYOND FLUX

SCALABLE FRONTEND ARCHITECTURES SCALABLE FRONTEND ARCHITECTURES USING PUBLISH/SUBSCRIBE USING PUBLISH/SUBSCRIBE

Michael Kurze @ goto Amsterdam 2016

slide-2
SLIDE 2

2

WHY ARCHITECTURE? WHY ARCHITECTURE?

IT'S ABOUT TIME! IT'S ABOUT TIME!

2012-10-12 2014-05-13 2011-04-01 2016-04-01 100 - 200 - 300 - 400 - 0 - JS Transfer / kB

(Source: httparchive.org) We are building complex sofuware (requires tools), and we are talking about them (requires a language).

slide-3
SLIDE 3

3

COMPLEXITY COMPLEXITY

PLATFORM COMPLEXITY PLATFORM COMPLEXITY DRIVING DRIVING APPLICATION COMPLEXITY APPLICATION COMPLEXITY

slide-4
SLIDE 4

4

REACTIVITY & RESPONSIVENESS REACTIVITY & RESPONSIVENESS

synchronize state (across devices) provide immediate feedback continuous bi-directional communication (client/server) http://www.reactivemanifesto.org

slide-5
SLIDE 5

5

SCALEABILITY SCALEABILITY

slide-6
SLIDE 6

6

UNIDIRECTIONAL FLOW UNIDIRECTIONAL FLOW

slide-7
SLIDE 7

7

ONCE UPON A TIME ... ONCE UPON A TIME ...

UI inconsistencies in Facebook chat

slide-8
SLIDE 8

8

FLUX FLUX

slide-9
SLIDE 9
slide-10
SLIDE 10

10

FLUX - EXAMPLE FLUX - EXAMPLE

https://github.com/voronianski/flux-comparison

slide-11
SLIDE 11

11 . 1

FLUX - EXAMPLE - VIEW (1/3) FLUX - EXAMPLE - VIEW (1/3)

var var React React = = require require( ('react' 'react') ); ; var var FluxibleMixin FluxibleMixin = = require require( ('fluxible' 'fluxible') ). .FluxibleMixin FluxibleMixin; ; var var ProductStore ProductStore = = require require( ('./stores/ProductStore' './stores/ProductStore') ); ; var var addToCart addToCart = = require require( ('./actions/addToCart' './actions/addToCart') ); ; var var Products Products = = React React. .createClass createClass( ({ { mixins mixins: : [ [FluxibleMixin FluxibleMixin] ], , render render: : function function( () ) { { return return < <ul ul> > { {this this. .state state. .products products. .map map( (product product = => > < <li li> > < <img img src src= ={ {product product. .image image} }/> /> { {product product. .title title} }

  • {

{product product. .price price} }€ € < <button button

  • nClick
  • nClick=

={ {( () ) = => > this this. .addToCart addToCart( (product product) )} } disabled disabled= ={ {product product. .inventory inventory === === 0} }> >add add</ </button button> > </ </li li> > ) )} } </ </ul ul> >; ; } }, , addToCart addToCart: : function function( (product product) ) { { this this. .executeAction executeAction( (addToCart addToCart, , { { product product: : product product } }) ); ; } }, , // ... // ... } }) ); ;

slide-12
SLIDE 12

11 . 2

FLUX - EXAMPLE - VIEW (2/3) FLUX - EXAMPLE - VIEW (2/3)

var var React React = = require require( ('react' 'react') ); ; var var FluxibleMixin FluxibleMixin = = require require( ('fluxible' 'fluxible') ). .FluxibleMixin FluxibleMixin; ; var var ProductStore ProductStore = = require require( ('./stores/ProductStore' './stores/ProductStore') ); ; var var addToCart addToCart = = require require( ('./actions/addToCart' './actions/addToCart') ); ; var var Products Products = = React React. .createClass createClass( ({ { // ...render, addToCart... // ...render, addToCart... getInitialState getInitialState: : function function( () ) { { return return this this. ._getStateFromStores _getStateFromStores( () ); ; } }, , _getStateFromStores _getStateFromStores: : function function( () ) { { return return { { products products: : this this. .getStore getStore( (ProductStore ProductStore) ). .getAllProducts getAllProducts( () ) } }; ; } }, , statics statics: : { { storeListeners storeListeners: : { { _onChange _onChange: : [ [ProductStore ProductStore] ] } } } }, , _onChange _onChange: : function function( () ) { { this this. .setState setState( (this this. ._getStateFromStores _getStateFromStores( () )) ); ; } } } }) ); ;

slide-13
SLIDE 13

11 . 3

FLUX - EXAMPLE - VIEW (3/3) FLUX - EXAMPLE - VIEW (3/3)

var var React React = = require require( ('react' 'react') ); ; var var FluxibleMixin FluxibleMixin = = require require( ('fluxible' 'fluxible') ). .FluxibleMixin FluxibleMixin; ; var var ProductStore ProductStore = = require require( ('./stores/ProductStore' './stores/ProductStore') ); ; var var addToCart addToCart = = require require( ('./actions/addToCart' './actions/addToCart') ); ; var var Products Products = = React React. .createClass createClass( ({ { /* ... */ /* ... */ } }) ); ; var var ShoppingCart ShoppingCart = = require require( ('./components/CartContainer.jsx' './components/CartContainer.jsx') ); ; var var App App = = React React. .createClass createClass( ({ { mixins mixins: : [ [FluxibleMixin FluxibleMixin] ], , render render: : function function( () ) { { return return < <div div> > < <Products Products /> /> < <ShoppingCart ShoppingCart /> /> </ </div div> >; ; } } } }) ); ; export export function function render render( (context context) ) { { React React. .withContext withContext( ( context context. .getComponentContext getComponentContext( () ), , function function( () ) { { React React. .render render( ( React React. .createElement createElement( (App App) ), , document document. .getElementById getElementById( ('fluxible-app' 'fluxible-app') ) ) ); ; } } ) ); ; } }

slide-14
SLIDE 14

12

FLUX - EXAMPLE - ACTION FLUX - EXAMPLE - ACTION

var var shop shop = = require require( ('../../../api/shop' '../../../api/shop') ); ; // actions/addToCart.js // actions/addToCart.js module module. .exports exports = = function function ( (context context, , payload payload, , done done) ) { { context context. .dispatch dispatch( ('ADD_TO_CART' 'ADD_TO_CART', , { { product product: : payload payload. .product product } }) ); ; done done( () ); ; } }; ; // actions/cartCheckout.js // actions/cartCheckout.js module module. .exports exports = = function function ( (context context, , payload payload, , done done) ) { { var var products products = = payload payload. .products products; ; context context. .dispatch dispatch( ('CART_CHECKOUT' 'CART_CHECKOUT') ); ; shop shop. .buyProducts buyProducts( (products products, , function function ( () ) { { context context. .dispatch dispatch( ('SUCCESS_CHECKOUT' 'SUCCESS_CHECKOUT', , { { products products: : products products } }) ); ; done done( () ); ; } }) ); ; } }; ;

slide-15
SLIDE 15

13

FLUX - EXAMPLE - DISPATCHER FLUX - EXAMPLE - DISPATCHER

var var Fluxible Fluxible = = require require( ('fluxible' 'fluxible') ); ; var var CartStore CartStore = = require require( ('./stores/CartStore' './stores/CartStore') ); ; var var ProductStore ProductStore = = require require( ('./stores/ProductStore' './stores/ProductStore') ); ; var var app app = = new new Fluxible Fluxible( () ); ; app app. .registerStore registerStore( (CartStore CartStore) ); ; app app. .registerStore registerStore( (ProductStore ProductStore) ); ; var var view view = = require require( ('./view' './view') ) var var receiveProducts receiveProducts = = require require( ('./actions/receiveProducts' './actions/receiveProducts') ); ; var var context context = = app app. .createContext createContext( () ); ; context context. .executeAction executeAction( (receiveProducts receiveProducts, , { {} }, , function function ( (err err) ) { { if if ( (err err) ) { { throw throw err err; ; } } return return view view. .render render( (context context) ); ; } }) ); ;

slide-16
SLIDE 16

14

FLUX - EXAMPLE - STORES FLUX - EXAMPLE - STORES

var var createStore createStore = = require require( ('fluxible/addons/createStore' 'fluxible/addons/createStore') ); ; var var ProductStore ProductStore = = createStore createStore( ({ { storeName storeName: : 'ProductStore' 'ProductStore', , initialize initialize: : function function ( () ) { { this this. ._products _products = = [ [] ]; ; } }, , handlers handlers: : { { 'ADD_TO_CART' 'ADD_TO_CART': : 'decreaseInventory' 'decreaseInventory', , 'RECEIVE_PRODUCTS' 'RECEIVE_PRODUCTS': : 'handleReceive' 'handleReceive' } }, , handleReceive handleReceive: : function function ( (payload payload) ) { { this this. ._products _products = = payload payload. .products products; ; this this. .emitChange emitChange( () ); ; } }, , decreaseInventory decreaseInventory: : function function ( (payload payload) ) { { this this. .dispatcher dispatcher. .waitFor waitFor( ('CartStore' 'CartStore', , function function( () ) { { var var product product = = payload payload. .product product; ; product product. .inventory inventory = = Math Math. .max max( (product product. .inventory inventory-1

  • 1,

, 0) ); ; this this. .emitChange emitChange( () ); ; } }) ); ; } }, , getAllProducts getAllProducts: : function function ( () ) { { return return this this. ._products _products; ; } } } }) ); ; module module. .exports exports = = ProductStore ProductStore; ;

slide-17
SLIDE 17

15

ADVANTAGES OF FLUX ADVANTAGES OF FLUX

compared to "classic" MVC (AngularJS, Backbone) strict separation: State / UI / Behavior each value stored exactly once clear data flow, good testability server-side rendering relatively simple

slide-18
SLIDE 18

16

DISADVANTAGES OF FLUX DISADVANTAGES OF FLUX

compared to "classic" MVC (AngularJS, Backbone) each store sees all actions hard-wired store-dependencies stores are (conceptually) singletons anti-pattern: transporting mutable state in actions

slide-19
SLIDE 19

17

REDUX REDUX

THE EVOLUTION THE EVOLUTION

(STATE, ACTION) → STATE (STATE, ACTION) → STATE

slide-20
SLIDE 20

18

REDUX REDUX

Flux:

slide-21
SLIDE 21

19

ADVANTAGES OF REDUX ADVANTAGES OF REDUX

  • vs. "vanilla" Flux

functional composition snapshot/replay capabilities can yield performance boost (free dirty-checking)

slide-22
SLIDE 22

20

DISADVANTAGES OF REDUX DISADVANTAGES OF REDUX

  • vs. "vanilla" Flux

must protect against mutating state, using e.g. spread constructor {...oldState, prop: newVal} (ES201?) (test with) recursive Object.freeze() Immutable.js potentially steep learning curve (functional paradigm) container components know the whole state tree

slide-23
SLIDE 23

21

THE REVOLUTION THE REVOLUTION

PUBLISH-SUBSCRIBE PUBLISH-SUBSCRIBE

an old pattern from the 80ies

slide-24
SLIDE 24

22

DECOUPLING DECOUPLING

full decoupling of communicating components: sender does not know about recipient(s) recipient does not know about sender How? Broadcast of events through central bus selection of receivers through topics

slide-25
SLIDE 25

23

PUBSUB ARCHITECTURE PUBSUB ARCHITECTURE

event bus: connects senders/receivers (asynchronously) view components, like in Flux/Redux (+ create action events) activity components, like Flux stores (+ completion of async actions) unidirectional flow using event patterns

slide-26
SLIDE 26

24

PUBSUB ARCHITECTURE PUBSUB ARCHITECTURE

TOPICS FOR SCALABILITY TOPICS FOR SCALABILITY

topics for resources (state slices) and actions (intents to modify) topics connect components in a configurable way

slide-27
SLIDE 27

25 . 1

PUBSUB (LAXARJS) - EXAMPLE - VIEW COMPONENT (1/2) PUBSUB (LAXARJS) - EXAMPLE - VIEW COMPONENT (1/2)

import import React React from from 'react' 'react'; ; import import patterns patterns from from 'laxar-patterns' 'laxar-patterns'; ; const const injections injections = = [ ['axContext' 'axContext', , 'axReactRender' 'axReactRender'] ]; ; function function create create( (context context, , reactRender reactRender) ) { { patterns patterns. .resources resources. .handlerFor handlerFor( (context context) ) . .registerResourceFromFeature registerResourceFromFeature( ('products' 'products', , render render) ); ; function function render render( () ) { { reactRender reactRender( (< <ul ul> > { {( (context context. .resources resources. .products products || || [ [] ]) ). .map map( (product product = => > < <li li> > < <img img src src= ={ {product product. .image image} }/> /> { {product product. .title title} }

  • {

{product product. .price price} }€ € < <button button

  • nClick
  • nClick=

={ {( () ) = => > addToCart addToCart( (product product) )} } disabled disabled= ={ {product product. .inventory inventory === === 0} }> >add add</ </button button> > </ </li li> > ) )} } </ </ul ul> >) ); ; } } // ... addToCart ... // ... addToCart ...

slide-28
SLIDE 28

25 . 2

PUBSUB (LAXARJS) - EXAMPLE - VIEW COMPONENT (2/2) PUBSUB (LAXARJS) - EXAMPLE - VIEW COMPONENT (2/2)

import import React React from from 'react' 'react'; ; import import patterns patterns from from 'laxar-patterns' 'laxar-patterns'; ; const const injections injections = = [ ['axContext' 'axContext', , 'axReactRender' 'axReactRender'] ]; ; function function create create( (context context, , reactRender reactRender) ) { { // ... render ... // ... render ... const const addToCartPublisher addToCartPublisher = = patterns patterns. .actions actions. .publisherForFeature publisherForFeature( (context context, , 'addToCart' 'addToCart') ) function function addToCart addToCart( (product product) ) { { if if( (product product. .inventory inventory > > 0) ) { { addToCartPublisher addToCartPublisher( ({ { product product: : product product } }) ); ; } } } } return return { { onDomAvailable

  • nDomAvailable:

: render render } }; ; } } export export default default { { name name: : 'product-list-view' 'product-list-view', , injections injections: : injections injections, , create create: : create create } }; ;

slide-29
SLIDE 29

26 . 1

PUBSUB (LAXARJS) - EXAMPLE - EVENTS PUBSUB (LAXARJS) - EXAMPLE - EVENTS

{ { "widget" "widget": : "product-list-view" "product-list-view", , "features" "features": : { { "addToCart" "addToCart": : { { "action" "action": : "add-to-cart" "add-to-cart" } }, , "products" "products": : { { "resource" "resource": : "product-list" "product-list" } } } } } }, , { { "widget" "widget": : "shopping-cart-view" "shopping-cart-view", , "features" "features": : { { . .. .. . } } } }, , { { "widget" "widget": : "products-activity" "products-activity", , "features" "features": : { { "products" "products": : { { "resource" "resource": : "product-list" "product-list" } }, , "decrementInventory" "decrementInventory": : { { "onActions" "onActions": : [ ["add-to-cart" "add-to-cart"] ] } } } } } }, , { { "widget" "widget": : "shopping-cart-activity" "shopping-cart-activity", , "features" "features": : { { . .. .. . } } } }

slide-30
SLIDE 30

27 . 1

PUBSUB (LAXARJS) - EXAMPLE - ACTIVITY COMPONENT (1/2) PUBSUB (LAXARJS) - EXAMPLE - ACTIVITY COMPONENT (1/2)

import import ax ax from from 'laxar' 'laxar'; ; import import patterns patterns from from 'laxar-patterns' 'laxar-patterns'; ; import import shop shop from from '../../../api' '../../../api'; ; const const injections injections = = [ ['axContext' 'axContext'] ]; ; function function create create( (context context) ) { { let let products products = = [ [] ]; ; const const replaceProducts replaceProducts = = patterns patterns. .resources resources. .replacePublisherForFeature replacePublisherForFeature( (context context, , 'products' 'products') ); ; context context. .eventBus eventBus. .subscribe subscribe( ('beginLifecycleRequest' 'beginLifecycleRequest', , ( () ) = => > { { shop shop. .getProducts getProducts( (list list = => > { { products products = = list list; ; replaceProducts replaceProducts( (products products) ); ; } }) ); ; } }) ); ; // ... action handling (next) ... // ... action handling (next) ... } } export export default default { { name name: : 'products-activity' 'products-activity', , injections injections: : injections injections, , create create: : create create } }; ;

slide-31
SLIDE 31

27 . 2

PUBSUB (LAXARJS) - EXAMPLE - ACTIVITY COMPONENT (2/2) PUBSUB (LAXARJS) - EXAMPLE - ACTIVITY COMPONENT (2/2)

import import ax ax from from 'laxar' 'laxar'; ; import import patterns patterns from from 'laxar-patterns' 'laxar-patterns'; ; import import shop shop from from '../../../api' '../../../api'; ; const const injections injections = = [ ['axContext' 'axContext'] ]; ; function function create create( (context context) ) { { // ... products initialization (previous) ... // ... products initialization (previous) ... patterns patterns. .actions actions. .handlerFor handlerFor( (context context) ) . .registerActionsFromFeature registerActionsFromFeature( ('decrementInventory' 'decrementInventory', , ( ({ { product product } }) ) = => > { { products products . .filter filter( (current current = => > current current. .id id == == product product. .id id) ) . .forEach forEach( (current current = => > { { current current. .inventory inventory = = Math Math. .max max( (0 0, , current current. .inventory inventory -

  • 1

1) ); ; } }) ); ; replaceProducts replaceProducts( (products products) ); ; } }) ); ; } } export export default default { { name name: : 'products-activity' 'products-activity', , injections injections: : injections injections, , create create: : create create } }; ;

slide-32
SLIDE 32

28

PUBSUB IN PRACTICE PUBSUB IN PRACTICE

FROM EVENT BUS TO FRAMEWORK FROM EVENT BUS TO FRAMEWORK http://www.laxarjs.org component configuration lifecycle: when are event collaborators available? services for view components: where do I render my HTML to? development tools (event log, visual composition structure)

slide-33
SLIDE 33

29

ADVANTAGES OF PUB/SUB ADVANTAGES OF PUB/SUB

  • vs. Flux/Redux

decoupling of components visible architecture flexibility of composition (patterns: resources, actions, flags, streams...) freedom of implementation (functional vs. imperative, React vs. AngularJS...)

slide-34
SLIDE 34

30

DISADVANTAGES OF PUB/SUB DISADVANTAGES OF PUB/SUB

  • vs. Flux/Redux

ecosystem small freedom to make mistakes server-side rendering not there yet

slide-35
SLIDE 35

31

CONCLUSION CONCLUSION

IS THIS EVEN FLUX?! IS THIS EVEN FLUX?!

"I define Flux as 'unidirectional data flow with changes described as plain objects'." ( , Author of Redux) Dan Abramov "It's cool that you are inventing a better Flux by not doing Flux at all." ( , Author of Cycle.js) André Staltz

slide-36
SLIDE 36

32

CONCLUSION* CONCLUSION*

Flux Redux PubSub unidirectional flow + + + complexity handling

  • +

+ reactivity

  • +

+ scalability (component isolation)

  • +

ecosystem

  • +
  • learning curve

+

  • +

snapshot/record/replay

  • +
  • longevity
  • ?

+ * YOUR MILEAGE MAY VARY * YOUR MILEAGE MAY VARY

Redux PubSub

slide-37
SLIDE 37

33

BEYOND FLUX BEYOND FLUX

Slides: github.com/x1b/beyond-flux-goto2016 Michael Kurze ( ) @0b11011 laxarjs.org

slide-38
SLIDE 38

34

IMAGES IMAGES

"The ultimate Swiss Army Knife for sale in Interlaken", , Lizenz: , use without modification "1913 photograph Ford company, USA", , Lizenz: "Mark Zuckerberg Facebook SXSWi 2008 Keynote", , Lizenz: , use without modification "Erdmännchen, Zoo, Tier, Sand, Wüste", Pixabay / Didgeman , Lizenz: "Magic Cube, Patience Games, Puzzle", Pixabay / Hans , Lizenz: "Two wedding rings lying on a white surface.", , Lizenz: , use without modification https://www.flickr.com/photos/redjar/113974357 CC BY-SA 2.0 https://en.wikipedia.org/wiki/File:A-line1913.jpg Public Domain https://www.flickr.com/photos/99565773@N00/2323729121 CC BY 2.0 https://pixabay.com/de/erdm%C3%A4nnchen-zoo-tier-sand-w%C3%BCste-363051 CC0 1.0 Public Domain https://pixabay.com/en/magic-cube-patience-games-puzzle-232282 CC0 1.0 Public Domain http://www.picserver.org/w/wedding-rings01.html CC BY-SA 3.0