Pro Yearly is on sale from $80 to $50! »

Dynamics of Change: Why Reactivity Matters

Dynamics of Change: Why Reactivity Matters

Given at PolyConf 2016. When software grows in complexity due to complex requirements, managing concurrent modifications between modules becomes an important challenge. Two general strategies are applicable: passive and reactive. In this talk we will see when each strategy is advantageous, and how the reactive strategy is a sensible default.

C8dbe3c6d219999ee0ecce86a450d0e3?s=128

André Staltz

June 30, 2016
Tweet

Transcript

  1. Dynamics of Change Why Reactivity matters @andrestaltz

  2. Reactive?


  3. None
  4. Product Cart Invoice Coupon Payment User profile Sale Vendor

  5. Cart Invoice

  6. Cart Invoice When a new 
 product is added... ...update

    the total
 amount charged.
  7. Cart Invoice

  8. Cart Invoice

  9. fun addProduct(product: Product) {
 // ...
 Invoice.updateInvoicing(product)
 } Cart Invoice

  10. package my.project import my.project.Invoice public object Cart { fun addProduct(product:

    Product) { // ... Invoice.updateInvoicing(product) } }
  11. fun addProduct(product: Product) {
 // ...
 Invoice.updateInvoicing(product)
 } Cart Invoice

    Proactive
 Responsible for the change Passive
 Unaware of the dependency
  12. fun addProduct(product: Product) {
 // ...
 Invoice.updateInvoicing(product)
 } Cart Invoice

    incrementCounter must be public
  13. Cart Invoice Passive Programming

  14. Cart Invoice Cart.onProductAdded { product -> this.updateInvoicing(product) }

  15. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  16. Cart.onProductAdded { product -> this.updateInvoicing(product) } Listenable
 Unaware of the

    dependency Reactive
 Responsible 
 for the change Cart Invoice
  17. add Product event must be public Cart Invoice Cart.onProductAdded {

    product -> this.updateInvoicing(product) }
  18. Cart Invoice Reactive Programming

  19. Cart Invoice Passive Programming Cart Invoice Reactive Programming Remote setters

    and updates Events, observation, and self-updates
  20. "Product added" event 
 in the Cart Update invoice method

    
 in the Invoice Passive public Reactive public
  21. None
  22. Product Cart Invoice Coupon Payment User profile Sale Vendor

  23. How does it work? What does it affect?

  24. Cart Invoice Coupon Payment Sale

  25. Cart Invoice Coupon Payment Sale What does it affect?

  26. Cart Invoice Coupon Payment Sale What does it affect? Look

    inside
  27. Cart Invoice Coupon Payment Sale How does it work?

  28. Cart Invoice Coupon Payment Sale How does it work? Find

    usages of its methods
  29. Cart Invoice Coupon Payment Sale

  30. Cart Invoice Coupon Payment Sale What does it affect?

  31. Cart Invoice Coupon Payment Sale What does it affect? Find

    usages of its events
  32. Cart Invoice Coupon Payment Sale How does it work?

  33. Cart Invoice Coupon Payment Sale How does it work? Look

    inside
  34. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  35. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  36. Cart Invoice Irresponsible
 modules
 Passive everywhere

  37. Example How do you set up analytics events?

  38. Analytics LoginPage ProfilePage FrontPage Analytics.sendEvent()

  39. Analytics LoginPage ProfilePage FrontPage

  40. Analytics LoginPage ProfilePage FrontPage Cross-cutting concern? Aspect-oriented programming!

  41. package my.project public object LoginPage { fun init() { //

    ... } }
  42. pointcut opened(): call(void LoginPage.init()) after(): opened() { Analytics.sendEvent("Just opened LoginPage");

    } "Pointcut" "Advice" package my.project public object LoginPage { fun init() { // ... } }
  43. pointcut opened(): call(void LoginPage.init()) after(): opened() { Analytics.sendEvent("Just opened LoginPage");

    } "Pointcut" "Advice" package my.project public object LoginPage { fun init() { // ... } } After the execution of the program hits "opened", run that "advice" snippet
  44. package my.project public object LoginPage { fun init() { //

    ... this.broadcastEvent("opened") } fun addListener(event: String, listener: () -> Unit) { // ... } } Reactive, with events
  45. package my.project.utils import my.project.LoginPage public object Analytics { fun sendEvent(msg:

    String) { // ... } fun setup() { LoginPage.addListener("opened", { () -> this.sendEvent("Just opened LoginPage") }) } } Reactive, with events
  46. Problem Still using passive programming

  47. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  48. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  49. Cart Invoice

  50. Cart Invoice Setup Update

  51. No setter or update methods?

  52. None
  53. None
  54. Cart Book Book Shoes

  55. Cart Book Book Shoes Invoice €23,50 €72,40 =SUM(Cart.Products)

  56. Cart Book Book Shoes Book Shoes Watch Invoice €23,50 €72,40

    €346,90 =SUM(Cart.Products)
  57. Cart Book Shoes Watch Invoice €23,50 €72,40 €346,90 =SUM(Cart.Products)

  58. Cart Book Shoes Watch Invoice €23,50 €72,40 €346,90 =SUM(Cart.Products) Delayed

    =DELAY(Invoice, 1000ms) €23,50 €72,40 €346,90
  59. Cart Book Shoes Watch Invoice €23,50 €72,40 €346,90 =SUM(Cart.Products) Delayed

    =DELAY(Invoice, 1000ms) €23,50 €72,40 €346,90 Functional and Reactive Programming RxJava RxJS ReactiveCocoa
  60. €23,50 €72,40 €346,90 A stream of values over time

  61. €23,50 €72,40 €346,90 A stream of values over time "A

    magical spreadsheet cell"
  62. package my.project import my.project.Cart public object Invoice { fun updateInvoicing(product:

    Product) { // ... } fun setup() { Cart.onProductAdded { product -> this.updateInvoicing(product) } } }
  63. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  64. Problem Singletons everywhere


  65. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } Singleton Singleton
  66. Passive everywhere => irresponsible modules Reactive everywhere => Singletons everywhere

  67. Passive is good for: Data structures Dependency injection

  68. Dependencies What? versus How?

  69. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } "What"
  70. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } "How"
  71. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } } Bad
  72. Cart Invoice Passive
 Unaware of the dependency

  73. Reactive
 Responsible 
 for the change Cart Invoice

  74. How does it work? Reactive: look inside What does it

    depend on? Passive: unaware of the
 dependency Passively Reactive: 
 the best of two worlds
  75. package my.project import my.project.Cart public object Invoice { fun getInvoice():

    Observable<Float> { val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  76. package my.project public object Invoice { fun getInvoice(): Observable<Float> {

    val invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  77. package my.project public Invoice { fun getInvoice(): Observable<Float> { val

    invoice = Cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  78. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  79. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  80. package my.project public Invoice { public var cart: Cart? =

    null fun getInvoice(): Observable<Float>? { val invoice = this.cart?.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  81. package my.project public Invoice { fun getInvoice(cart: Cart): Observable<Float> {

    val invoice = cart.productAddedObservable .fold(0) { total, product -> total + product.price } return invoice } }
  82. package my.project fun invoice(cart: Cart): Observable<Float> { return cart.productAddedObservable .fold(0)

    { total, product -> total + product.price } }
  83. package my.project fun invoice(p: Observable<Product>): Observable<Float> { return p .fold(0)

    { total, product -> total + product.price } }
  84. package my.project fun invoice(p: Observable<Product>): Observable<Float> { return p .fold(0)

    { total, product -> total + product.price } }
  85. Input is a stream Function Output is a stream

  86. Input is a stream Function Output is a stream The

    dependency of the output 
 is the input
  87. Input is a stream Function Output is a stream We

    can make this a pure function
  88. 
 stream Function stream
 Observer Side effect
 (e.g. Analytics.sendEvent)

  89. stream Function stream
 Observer Write 
 Click stream Read

  90. stream Function Write Read stream Function stream stream Function stream

  91. stream Function Write Read stream Function stream stream Function stream

    Pure logic Effects
  92. Cycle.js.org A functional and reactive JavaScript framework 
 for cleaner

    code
  93. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1); let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1); let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks; } Cycle.run(main, { DOM: makeDOMDriver('#main-container') });
  94. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1); let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1); let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  95. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$); let count$ = action$.fold((x,y) => x + y, 0); let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  96. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$) let count$ = action$.fold((x,y) => x + y, 0) let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ }; return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  97. import xs from 'xstream' import Cycle from '@cycle/xstream-run' import {div,

    button, p, makeDOMDriver} from '@cycle/dom' function main(sources) { let dec$ = sources.DOM.select('.dec').events('click').mapTo(-1) let inc$ = sources.DOM.select('.inc').events('click').mapTo(+1) let action$ = xs.merge(inc$, dec$) let count$ = action$.fold((x,y) => x + y, 0) let vdom$ = count$.map(count => div([ button('.dec', 'Decrement'), button('.inc', 'Increment'), p(`Counter: ${count}`) ]) ); let sinks = { DOM: vdom$ } return sinks } Cycle.run(main, { DOM: makeDOMDriver('#main-container') })
  98. None
  99. stream Function Write Read stream Function stream stream Function stream

    Stream I/O in Haskell 1.0 Dataflow languages Visual programming
  100. Cart Invoice Passive Programming Cart Invoice Reactive Programming Remote setters

    and updates Events, observation, and self-updates
  101. Passive Reactive How does it work? Find usages Look inside

    What does it affect? Look inside Find usages
  102. stream Function Write Read stream Function stream stream Function stream

    Functional programming Moving effects to the edges of the system
  103. Thank you 
 & Questions?