Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Make an Event of It! (CodeMash 2014)

Make an Event of It! (CodeMash 2014)

The upsurge in asynchronous programming has brought event-driven patterns to the forefront. But even if you aren't re-writing your app in Node.js, evented patterns can improve your application. This talk demonstrates concrete examples of how events can benefit the various layers of your application. It shows how events can pitch in the fight against fat controllers (and fat models too!) It applies eventing to simplify testing and decouple our app from external dependencies. It even reveals how eventing can help shape data to provide flexibility and auditing.

Jason R Clark

January 09, 2014
Tweet

More Decks by Jason R Clark

Other Decks in Technology

Transcript

  1. Jason Clark @jasonrclark Ruby Agent Engineer Make an Event of

    It! Evented Patterns in Ruby Thursday, January 9, 14
  2. Where Are We Going? • The Pattern • Internal Coupling

    • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 2 Thursday, January 9, 14
  3. The Pattern http://flic.kr/p/6K9jC4 Thursday, January 9, 14

  4. 4 Thursday, January 9, 14

  5. 5 Thursday, January 9, 14

  6. 5 Thursday, January 9, 14

  7. 5 Thursday, January 9, 14

  8. Events Aren't (Necessarily) 6 • Asynchronous • IO related •

    Distributed • Complicated Thursday, January 9, 14
  9. 7 Thursday, January 9, 14

  10. 7 Notifier Thursday, January 9, 14

  11. 7 Notifier Subscriber Subscriber Subscriber Thursday, January 9, 14

  12. 7 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  13. 7 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  14. 7 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  15. 8 Why Not Methods? Thursday, January 9, 14

  16. 9 Thursday, January 9, 14

  17. 9 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  18. 9 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  19. Why Not Callbacks? 10 Thursday, January 9, 14

  20. 11 class Subscription < ActiveRecord::Base before_create :record_signup private def record_signup

    self.signed_up_on = Date.today end end Thursday, January 9, 14
  21. 11 class Subscription < ActiveRecord::Base before_create :record_signup private def record_signup

    self.signed_up_on = Date.today end end Thursday, January 9, 14
  22. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 12 Thursday, January 9, 14
  23. 13 Internal Coupling http://flic.kr/p/KikA9 Thursday, January 9, 14

  24. 14 newrelic_rpm Thursday, January 9, 14

  25. 14 app start newrelic_rpm Thursday, January 9, 14

  26. 14 app start connect New Relic Server newrelic_rpm Thursday, January

    9, 14
  27. {server config} 14 app start connect New Relic Server newrelic_rpm

    Thursday, January 9, 14
  28. {server config} 14 app start connect New Relic Server ?

    newrelic_rpm Thursday, January 9, 14
  29. 15 def connect() config = config_from_server() finish_setup(config) end newrelic_rpm Thursday,

    January 9, 14
  30. 16 def finish_setup(config) add_naming_rule(config['rules']) @cross_app_tracing.set_key(config) @js_instrumentor.log_config(config) @beacon = Beacon.new end

    newrelic_rpm Thursday, January 9, 14
  31. 17 def test_finish_setup_naming_rules config = { 'rules' => [...] }

    @agent.finish_setup(config) assert_equal 2, @agent.transaction_rules.size end newrelic_rpm Thursday, January 9, 14
  32. 18 def test_finish_setup_naming_rules @agent.cross_app_tracing.stub(:set_key) @agent.js_instrumentor.stub(:log_config) config = { 'rules' =>

    [...] } @agent.finish_setup(config) assert_equal 2, @agent.transaction_rules.size end newrelic_rpm Thursday, January 9, 14
  33. 19 def finish_setup(config) add_naming_rule(config['rules']) @cross_app_tracing.set_key(config) @js_instrumentor.log_config(config) @beacon = Beacon.new end

    newrelic_rpm Thursday, January 9, 14
  34. 20 class Beacon def initialize # Your code is bad

    and you should feel bad Net::HTTP.get("http://...") end end newrelic_rpm Thursday, January 9, 14
  35. 21 def test_finish_setup_naming_rules @agent.cross_app_tracing.stub(:set_key) @agent.js_instrumentor.stub(:log_config) Beacon.stub(:new).return(stub("fake beacon")) config = {

    'rules' => [...] } @agent.finish_setup(config) assert_equal 2, @agent.transaction_rules.size end newrelic_rpm Thursday, January 9, 14
  36. 22 def finish_setup(config) add_naming_rule(config['rules']) @cross_app_tracing.set_key(config) @js_instrumentor.log_config(config) @beacon = Beacon.new end

    newrelic_rpm Thursday, January 9, 14
  37. 23 def finish_setup(config) events.notify(:configured) end newrelic_rpm Thursday, January 9, 14

  38. 24 class Beacon def initialize events.subscribe(:configured) do Net::HTTP.get("http://...") end end

    end newrelic_rpm Thursday, January 9, 14
  39. 25 def test_finish_setup_notifies called = false @events.subscribe(:configured) do called =

    true end @agent.finish_setup({}) assert called end newrelic_rpm Thursday, January 9, 14
  40. 26 def test_add_naming_rules config = { 'rules' => [...] }

    @agent.add_naming_rules(config) assert_equal 2, @agent.transaction_rules.size end newrelic_rpm Thursday, January 9, 14
  41. More Focused Tests 27 Thursday, January 9, 14

  42. More Independent Classes 28 Thursday, January 9, 14

  43. Implicit Ordering 29 Thursday, January 9, 14

  44. Debugging 30 Thursday, January 9, 14

  45. Performance 31 Thursday, January 9, 14

  46. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 32 Thursday, January 9, 14
  47. http://flic.kr/p/euQpT External Coupling Thursday, January 9, 14

  48. 34 newrelic_rpm Thursday, January 9, 14

  49. 34 Javascript insertion newrelic_rpm Thursday, January 9, 14

  50. 34 Javascript insertion Error collection newrelic_rpm Thursday, January 9, 14

  51. 34 Javascript insertion Cross application tracing Error collection newrelic_rpm Thursday,

    January 9, 14
  52. 35 No Setup Please! newrelic_rpm Thursday, January 9, 14

  53. 36 module NewRelic::Rack class AgentHooks def call(env) notify(:before_call, env) result

    = @app.call(env) notify(:after_call, env, result) result end end end newrelic_rpm Thursday, January 9, 14
  54. 37 class CrossAppTracing def initialize events.subscribe(:before_call) do |e| process_request(e) end

    ... end end newrelic_rpm Thursday, January 9, 14
  55. Ever Heard of Composition? 38 Thursday, January 9, 14

  56. Looser Coupling 39 Thursday, January 9, 14

  57. Compatibilty Layer 40 Thursday, January 9, 14

  58. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 41 Thursday, January 9, 14
  59. http://flic.kr/p/dYor95 Adding Eventing Thursday, January 9, 14

  60. 43 Thursday, January 9, 14

  61. 43 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  62. 43 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  63. 44 gem install simple_events http://github.com/jasonrclark/simple_events Thursday, January 9, 14

  64. 45 class SimpleEvents::Notifier def initialize @events = {} ... end

    end simple_events Thursday, January 9, 14
  65. 46 class SimpleEvents::Notifier ... def subscribe(event, &handler) @events[event] ||= []

    @events[event] << handler check_for_runaway_subscriptions(event) end ... end simple_events Thursday, January 9, 14
  66. 47 class SimpleEvents::Notifier ... def notify(event, *args) return unless @events.has_key?(event)

    @events[event].each do |handler| begin handler.call(*args) rescue => err logger.debug("Fail #{@event}", err) end end end end simple_events Thursday, January 9, 14
  67. 48 class SimpleEvents::Notifier ... def notify(event, *args) return unless @events.has_key?(event)

    @events[event].each do |handler| begin handler.call(*args) rescue => err logger.debug("Fail #{@event}", err) end end end end simple_events Thursday, January 9, 14
  68. 49 class SimpleEvents::Notifier ... def notify(event, *args) return unless @events.has_key?(event)

    @events[event].each do |handler| begin handler.call(*args) rescue => err logger.debug("Fail #{@event}", err) end end end end simple_events Thursday, January 9, 14
  69. 50 class SimpleEvents::Notifier ... def notify(event, *args) return unless @events.has_key?(event)

    @events[event].each do |handler| begin handler.call(*args) rescue => err logger.debug("Fail #{@event}", err) end end end end simple_events Thursday, January 9, 14
  70. 51 class SimpleEvents::Notifier ... def notify(event, *args) return unless @events.has_key?(event)

    @events[event].each do |handler| begin handler.call(*args) rescue => err logger.debug("Fail #{@event}", err) end end end end simple_events Thursday, January 9, 14
  71. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 52 Thursday, January 9, 14
  72. ActiveSupport:: Notifications http://flic.kr/p/aWNwU2 Thursday, January 9, 14

  73. 54 AS::Notifications = ActiveSupport::Notifications activesupport Thursday, January 9, 14

  74. Rails Request 55 •action_dispatch.request •start_processing.action_controller •process_action.action_controller •sql.active_record •render_template.action_view • ...

    and more! Thursday, January 9, 14
  75. 56 events = [] AS::Notifications.subscribe("event") do |*args| events << *args

    end activesupport Thursday, January 9, 14
  76. 57 AS::Notifications.subscribe("event") do |*args| events << *args end ["render_template.action_view", "e2a1e92a3c613576c2b0"

    {:identifier=>"/file/path/here"}] activesupport Thursday, January 9, 14
  77. 58 e = AS::Notifications::Event.new(*args) e.name # => "render_template..." e.duration #

    => 10 (in milliseconds) e.payload # => { :identifier => ... } activesupport Thursday, January 9, 14
  78. 59 activesupport AS::Notifications.instrument("evt", :data => 1) Thursday, January 9, 14

  79. 60 AS::Notifications.instrument("evt", :data => 1) do #... your timed code

    here end activesupport Thursday, January 9, 14
  80. • Regexp on Notifications.subscribe • Event#parent_of? • Temporary subscription •

    Unsubscribe • LogSubscriber 61 activesupport Thursday, January 9, 14
  81. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Events • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 62 Thursday, January 9, 14
  82. http://flic.kr/p/a1dEec Notifiers Thursday, January 9, 14

  83. 64 Thursday, January 9, 14

  84. 64 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  85. 64 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  86. Naming 65 Thursday, January 9, 14

  87. Flexible Payload 66 Thursday, January 9, 14

  88. Primitives at Boundaries 67 Thursday, January 9, 14

  89. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Events • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 68 Thursday, January 9, 14
  90. 69 http://flic.kr/p/8w9PyF Subscribers Thursday, January 9, 14

  91. 70 Thursday, January 9, 14

  92. 70 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  93. 70 Notifier Subscriber Subscriber Subscriber Eventing System Thursday, January 9,

    14
  94. != self.only 71 Thursday, January 9, 14

  95. 72 Thursday, January 9, 14

  96. 73 class UserSignup < Mutations::Command def execute user = User.create!(inputs)

    NewsletterSubscriptions.create(user.id) UserMailer.async(:deliver_welcome, user.id) user end end Thursday, January 9, 14
  97. 74 class SomeSubscriber def initialize events.subscribe(:configured) do log_config end end

    def log_config ... end end Thursday, January 9, 14
  98. Memory Leaks 75 Thursday, January 9, 14

  99. Where Have We Been? • The Pattern • Internal Coupling

    • External Coupling • Adding Events • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data 76 Thursday, January 9, 14
  100. http://flic.kr/p/ndraB Events as Data Thursday, January 9, 14

  101. 78 Opened Account $100 Check 0001 -$15 Deposit $20 Check

    0002 -$21 Total $84 Bank Transactions Thursday, January 9, 14
  102. 79 Customer Pricing Thursday, January 9, 14

  103. 79 customer_id date plan 1 1/1/2013 basic 1 1/2/2013 premium

    1 2/1/2013 basic 1 2/2/2013 premium Customer Pricing Thursday, January 9, 14
  104. 79 customer_id date plan 1 1/1/2013 basic 1 1/2/2013 premium

    1 2/1/2013 basic 1 2/2/2013 premium Customer Pricing Thursday, January 9, 14
  105. Event Sourcing 80 Thursday, January 9, 14

  106. 81 http://flic.kr/p/4LTNc9 Thursday, January 9, 14

  107. 82 Metrics 10:00AM CPU/Utilization 0.85 Thursday, January 9, 14

  108. 83 Metrics 10:01AM CPU/Utilization 0.24 Thursday, January 9, 14

  109. 84 Metrics Thursday, January 9, 14

  110. 85 Metrics Thursday, January 9, 14

  111. 86 Metrics Thursday, January 9, 14

  112. 87 Metric Data Thursday, January 9, 14

  113. 87 Metric Data Thursday, January 9, 14

  114. 87 Metric Data Thursday, January 9, 14

  115. Other Examples 88 • Database replication • Log files •

    PubSub • Immutable data structures Thursday, January 9, 14
  116. Upsides 89 ★No Updates ★Automatic Audits ★Summarize/Transform/ Cache Thursday, January

    9, 14
  117. Downsides -So Much Data! -Hard to Query Efficiently -More Complex

    Code 90 Thursday, January 9, 14
  118. Jason Clark @jasonrclark Ruby Agent Engineer • The Pattern •

    Internal Coupling • External Coupling • Adding Eventing • ActiveSupport::Notifications • Notifiers • Subscribers • Events as Data ? Thursday, January 9, 14