Unilateral Data Flows in Javascript

Unilateral Data Flows in Javascript

Codice della presentazione: https://gist.github.com/cef62/ce0641a450a68f775318a3f26f1ddeb7

Una delle scelte più complesse da affrontare durante la realizzazione di una Single Page Application è quale pattern architetturale utilizzare. Esistono infiniti framework con moltissime interpretazioni dei principali pattern, e scegliere il più indicato per uno specifico progetto non è banale. Negli ultimi anni le architetture basate su flussi di dati unilaterali hanno dimostrato di essere particolarmente efficaci e, spesso, più facili da mantenere di architetture basate su interpretazioni del pattern MVC.

E' possibile ottenere flussi di dati unilaterali utilizzando tecniche molto diverse, durante il talk si vedranno alcuni degli approcci maggiormente diffusi cercando di comprenderne i diversi punti di forza e debolezza per poter scegliere la soluzione migliore a seconda del progetto da realizzare. Le tecniche presentate saranno indipendenti dal layer UI ed utilizzabili con i principali framework moderni basati su componenti.


Matteo Ronchi

July 05, 2016


  1. UNILATERAL DATA FLOWS in Javascript

  2. MATTEO RONCHI @cef62 github.com/cef62 Senior Engineer @ Staff member of

    Verona Frontenders Italian React & Angular Communities Maintainer
  3. ONCE UPON A TIME it was all about Model View

  4. Model View Controller User View ViewModel Model MVC MVVM manipulates

    updates interacts sees databinding mutates reads
  5. angular.module(‘my-module’, []) .factory(‘model’, function() { return { updateCount() { this.counter++

    } counter: 0 } }) .component(‘myComponent’, { template: ` <button ng-click=“$ctrl.onClick()”> Click Me {{$ctrl.model.counter}}</button>`, controller(model) { this.model = model this.onClick = () => model.update() } })
  6. FLUX PATTERN Frontend Architectures Evolution

  7. • Facebook interpretation of some well known patterns, like command

    and event-sourcing • First modern implementation of a unidirectional data flow architecture • Support multiple stores and envelopes request using actions
  8. View Store A Dispatcher Action Creator Actions Web API Store

    B User Interactions Change Events Pass Data To Stores Web Socket FLUX
  9. • Flux complexity grows with the number of stores •

    Huge code boilerplate required for managing actions and action-creators • No proper abstractions to manage complex async workflows FLUX PROBLEMS
  10. FLUX OFFSPRING Reflux Alt Redux NuclearJS Cerebral and many more…

  11. –Dan Abramov “Redux is a predictable state container for JavaScript

  12. • Currently the most used from the next-gen of Flux-like

    architectures • Really small Surface API • Rich ecosystem of DX Tools • Recognized from Facebook REDUX
  13. View User Interactions REDUX User Reducer A Reducer B Middlewares

    Actions Web API Web Socket Screen Change Events Pass Data To Store Diagram Inspired 
 by Andrè Staltz Store
  14. RX.JS • Can be used to implement Flux-like architectures •

    Offers powerful operators to create really complex async workflows • Can be used standalone or inside Redux • Have its own framework: Cycle.js

  16. “Components requests actions and redraws themselves when receive updated data.”

    “Views and data are completely decoupled and unaware of each other.”
  17. • Views/Components should be completely decoupled from stores • All

    mutations should happen through dispatch of actions • Async workflows should be isolated inside actions or middlewares • Stores doesn’t have mutation APIs but only getters and should emit change events
  18. const enableLoaderAction = () => ({ type: ENABLE_LOADER }) const

    syncAction = (doSomethingData) => ({ type: DO_SOMETHING, payload: doSomethingData }) // using redux-thunk syntax const asyncAction = (data) => (dispatcher) => invokeServerAPI(data) .then((res) => dispatcher(syncAction(res))) ACTIONS
  19. // using redux reducer syntax const appState = (state, {

    type, payload }) => { switch(type) { case ENABLE_LOADER: return setLoader(state, true) case DISABLE_LOADER: return setLoader(state, false) case DO_SOMETHING: return onDoSomething(state, payload) default return state } } STORE / REDUCERS
  20. const setLoader(state, showLoader) => { return Object.assign({}, state, { showLoader

    }) } const onDoSomething(state, info) => { return Object.assign({}, state, { info }) } STORE / REDUCERS
  21. ASYNC SEQUENCES Real world application often composes more async steps

    to achieve their goals
  22. “There are several ways to gain controls over complex async

    sequences of actions.”
 “A key point when choosing an async flow management pattern is the composability of different async blocks”
  23. THUNKS A thunk is a function that wraps an expression

    to delay its evaluation. It enables creation of chain of complex async operations. Can be very difficult to maintain for long sequences. Difficult to test.
  24. // (…args) => (dispatcher, getState) => {} const thunk =

    () => (dispatcher, getState) => { // retrieve current state const { info, user } = getState() // activate application loader dispatcher(enableLoader()) // load remote data http.get(`${info.url}/${user.id}`) .then((data) => { // update user data on the store dispatcher(updateUserData(data)) // deactivate application loader dispatcher(disableLoader()) }) }
  25. SAGAS An alternative side effect model for Redux apps. You

    create Sagas to gather all your Side Effects logic in a central place. Highly composable and easy to test. Can be complicate to serialize state with partial sagas execution.
  26. // worker Saga: fired on // USER_UPDATE_REQUESTED actions function* fetchUser({

    payload }) { try { // load user data from server const user = yield call(API.fetchUser, payload.userId) // pass received data to the store yield put(updateUserData(user)) } catch ({ message }) { // notify the store an error occurred yield put(updateUserError(message)) } } // Saga registered to redux-sagas middleware function* userSaga() { yield* takeLatest(USER_UPDATE_REQUESTED, fetchUser) }
  27. OBSERVABLES RxJS-based middleware for Redux. Compose and cancel async actions

    and more. The means to use reactive programming and composition to create async effects that dispatch actions to your redux reducer(s) Works with observables, promises and es2015 iterables. Highly composable and really terse syntax. Steep learning curve.
  28. // actions const fetchUser = (username) => ({ type: FETCH_USER,

    payload: username }) const fetchUserFulfilled = (payload) => ({ type: FETCH_USER_FULFILLED, payload }) const fetchUserRejected = (payload) => ({ type: FETCH_USER_REJECTED, payload, error: true }) // manager registered to redux-observable const fetchUserManager = (action$) => action$.ofType(FETCH_USER) .switchMap(({ payload }) => ajax.getJSON(`https…/${payload}`) .map(fetchUserFulfilled) .catch(({ xhr }) => of(fetchUserRejected(xhr.response))) )
  29. CUSTOM MIDDLEWARE It provides a third-party extension point between dispatching

    an action, and the moment it reaches the reducer. Can be used for logging, crash reporting, talking to an asynchronous API, routing, and more. Complete control and flexibility. Responsibility to test and maintains all the internal implementation.
  30. // action using middleware type const action = () =>

    ({ type: AJAX, url: ‘http://my-api.com', method: 'POST', body: ({ description, title }) => ({ title, description }), cb: (res) => console.log('Done!', res) }) // middleware implementation const middleware = ({ getState }) => (next) => (action) => { // if the type doesn’t match pass action to the next middleware if(action.type !== AJAX) return next(action) // retrieve required fields const {cb, url, method, body } = action // invoke remote endpoint http.get(url, { method, body: JSON.stringify(body(getState())) }) // pass response to action callback .then((response) => cb(response)) }

  32. • Enable next-gen developer tools (time-travel, advanced loggers) • Remote

    state serialization to support better issues replication • (potentially) Easy to test • Help decoupling completely data and views management

  34. • Require much code boilerplate • Describing application processes is

    quite verbose (not necessarily a cons) • Works better if the application is strongly data- driven and most of the application states are managed by the store
  35. SHOULD I USE A FRAMEWORK to benefit from an unilateral

    data flow architecture?
  36. class App extends React.Component { constructor() { super() this.state =

    reducer(undefined, {}) } dispatch(action) { this.setState(state => reducer(state, action)) } render() { return ( <Counter {...this.state} onIncrement={() => this.dispatch(increment())} onDecrement={() => this.dispatch(decrement())} /> ) } }

  38. • Redux • Cerebral • Cycle • ngStore (angular2 only)

    • try to learn Rx.js
  39. THANKS! @CEF62