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

Make an Event of It!

Make an Event of It!

Are your controllers jumbled with seemingly unrelated steps? Does testing any bit of application logic require fixtures and setup helpers a mile long?

Evented patterns create a vocabulary of what happens in your system, and a way to separate code triggering events from code that responds to them. That helps tame the sprawl by setting clean boundaries, simplifying tests, and keeping your dependencies isolated.

This talk reveals the power of events and what's already in Rails to help you.

92e7389893670a1920a4fd98aec0d246?s=128

Jason R Clark

April 22, 2014
Tweet

Transcript

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

    It! Evented Patterns in Ruby Tuesday, April 22, 14
  2. Storytime! Tuesday, April 22, 14

  3. Tuesday, April 22, 14

  4. Tuesday, April 22, 14

  5. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save redirect_to @user else render 'new' end end end Tuesday, April 22, 14
  6. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.welcome_email(@user).deliver redirect_to @user ... end end end Tuesday, April 22, 14
  7. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.welcome_email(@user).deliver Analytics.notify_user_creation(@user) redirect_to @user ... end end end Tuesday, April 22, 14
  8. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.welcome_email(@user).deliver Analytics.notify_user_creation(@user) Crm::Gateway.new(@user.id, @user.name, ...) redirect_to @user ... end end end Tuesday, April 22, 14
  9. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.welcome_email(@user).deliver Analytics.notify_user_creation(@user) Crm::Gateway.new(@user.id, @user.name, ...) Resque.enqueue(AlertTheSocials, @user.id) redirect_to @user ... end end end Tuesday, April 22, 14
  10. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.welcome_email(@user).deliver Analytics.notify_user_creation(@user) Crm::Gateway.new(@user.id, @user.name, ...) Resque.enqueue(AlertTheSocials, @user.id) redirect_to @user ... end end end Tuesday, April 22, 14
  11. https://flic.kr/p/6qdTC1 Tuesday, April 22, 14

  12. Where Are We Going? The Pattern Coupling Mechanics Responsibilities 12

    Tuesday, April 22, 14
  13. The Pattern http://flic.kr/p/6K9jC4 Tuesday, April 22, 14

  14. 14 Tuesday, April 22, 14

  15. 15 Tuesday, April 22, 14

  16. 15 Tuesday, April 22, 14

  17. 15 Tuesday, April 22, 14

  18. Events Aren't (Necessarily) 16 Asynchronous IO related Distributed Complicated Tuesday,

    April 22, 14
  19. 17 Tuesday, April 22, 14

  20. 17 Notifier Tuesday, April 22, 14

  21. 17 Notifier Subscriber Subscriber Subscriber Tuesday, April 22, 14

  22. 17 Notifier Subscriber Subscriber Subscriber Eventing System Tuesday, April 22,

    14
  23. 17 Notifier Subscriber Subscriber Subscriber Eventing System Tuesday, April 22,

    14
  24. 17 Notifier Subscriber Subscriber Subscriber Eventing System Tuesday, April 22,

    14
  25. 18 Why Not Methods? Tuesday, April 22, 14

  26. 19 Notifier Subscriber Subscriber Subscriber Eventing System Tuesday, April 22,

    14
  27. Why Not Callbacks? 20 Tuesday, April 22, 14

  28. 21 class Subscription < ActiveRecord::Base before_create :record_signup private def record_signup

    self.signed_up_on = Date.today end end Tuesday, April 22, 14
  29. 21 class Subscription < ActiveRecord::Base before_create :record_signup private def record_signup

    self.signed_up_on = Date.today end end Tuesday, April 22, 14
  30. Where Are We Going? The Pattern Coupling Mechanics Responsibilities 22

    Tuesday, April 22, 14
  31. 23 Internal Coupling http://flic.kr/p/KikA9 Tuesday, April 22, 14

  32. newrelic_rpm 24 Tuesday, April 22, 14

  33. newrelic_rpm 24 app start Tuesday, April 22, 14

  34. newrelic_rpm 24 app start connect() New Relic Server Tuesday, April

    22, 14
  35. newrelic_rpm {server config} 24 app start connect() New Relic Server

    Tuesday, April 22, 14
  36. newrelic_rpm {server config} 24 app start connect() New Relic Server

    ? Tuesday, April 22, 14
  37. newrelic_rpm 25 def connect() config = config_from_server() finish_setup(config) end Tuesday,

    April 22, 14
  38. newrelic_rpm 26 def finish_setup(config) add_naming_rule(config['rules']) @cross_app_tracing.set_key(config) @js_instrumentor.log_config(config) @beacon = Beacon.new

    end Tuesday, April 22, 14
  39. newrelic_rpm 27 def test_finish_setup_naming_rules config = { 'rules' => [...]

    } @agent.finish_setup(config) assert_equal 2, @agent.transaction_rules.size end Tuesday, April 22, 14
  40. 28 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 Tuesday, April 22, 14
  41. 29 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 Tuesday, April 22, 14
  42. 30 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 Tuesday, April 22, 14
  43. 31 class Beacon def initialize # Your code is bad

    and you should feel bad Net::HTTP.get("http://somewhere.com") end end newrelic_rpm Tuesday, April 22, 14
  44. 32 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 Tuesday, April 22, 14
  45. 33 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 Tuesday, April 22, 14
  46. 34 def finish_setup(config) events.notify(:configured) end newrelic_rpm Tuesday, April 22, 14

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

    end newrelic_rpm Tuesday, April 22, 14
  48. 36 def test_finish_setup_notifies called = false @events.subscribe(:configured) do called =

    true end @agent.finish_setup({}) assert called end newrelic_rpm Tuesday, April 22, 14
  49. 37 def test_add_naming_rules config = { 'rules' => [...] }

    @agent.add_naming_rules(config) assert_equal 2, @agent.transaction_rules.size end newrelic_rpm Tuesday, April 22, 14
  50. More Focused Tests 38 Tuesday, April 22, 14

  51. More Independent Classes 39 Tuesday, April 22, 14

  52. Domain Language 40 Tuesday, April 22, 14

  53. Implicit Ordering 41 Tuesday, April 22, 14

  54. Debugging 42 Tuesday, April 22, 14

  55. Performance 43 Tuesday, April 22, 14

  56. http://flic.kr/p/euQpT External Coupling Tuesday, April 22, 14

  57. 45 newrelic_rpm Tuesday, April 22, 14

  58. 45 Javascript insertion newrelic_rpm Tuesday, April 22, 14

  59. 45 Javascript insertion Error collection newrelic_rpm Tuesday, April 22, 14

  60. 45 Javascript insertion Cross application tracing Error collection newrelic_rpm Tuesday,

    April 22, 14
  61. 46 No Setup Please! newrelic_rpm Tuesday, April 22, 14

  62. 47 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 Tuesday, April 22, 14
  63. 48 class CrossAppTracing def initialize events.subscribe(:before_call) do |e| process_request(e) end

    ... end end newrelic_rpm Tuesday, April 22, 14
  64. Ever Heard of Composition? 49 Tuesday, April 22, 14

  65. Looser Coupling 50 Tuesday, April 22, 14

  66. Compatibilty Layer 51 Tuesday, April 22, 14

  67. Where Are We Going? The Pattern Coupling Mechanics Responsibilities 52

    Tuesday, April 22, 14
  68. http://flic.kr/p/dYor95 Adding Eventing Tuesday, April 22, 14

  69. 54 gem install simple_events http://github.com/jasonrclark/simple_events Tuesday, April 22, 14

  70. 55 class SimpleEvents::Notifier def initialize @events = {} ... end

    end simple_events Tuesday, April 22, 14
  71. 56 class SimpleEvents::Notifier ... def subscribe(event, &handler) @events[event] ||= []

    @events[event] << handler check_for_runaway_subscriptions(event) end ... end simple_events Tuesday, April 22, 14
  72. 57 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 Tuesday, April 22, 14
  73. 58 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 Tuesday, April 22, 14
  74. 59 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 Tuesday, April 22, 14
  75. 60 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 Tuesday, April 22, 14
  76. 61 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 Tuesday, April 22, 14
  77. ActiveSupport:: Notifications http://flic.kr/p/aWNwU2 Tuesday, April 22, 14

  78. 63 AS::Notifications = ActiveSupport::Notifications activesupport Tuesday, April 22, 14

  79. Typical Rails Request 64 • action_dispatch.request • start_processing.action_controller • process_action.action_controller

    • sql.active_record • render_template.action_view • ... and more! Tuesday, April 22, 14
  80. 65 events = [] AS::Notifications.subscribe("event") do |*args| events << *args

    end ["render_template.action_view", "e2a1e92a3c613576c2b0" {:identifier=>"/file/path/here"}] activesupport Tuesday, April 22, 14
  81. 66 e = AS::Notifications::Event.new(*args) e.name # => "render_template..." e.duration #

    => 10 (in milliseconds) e.payload # => { :identifier => ... } activesupport Tuesday, April 22, 14
  82. 67 AS::Notifications.instrument("evt", :data => 1) activesupport Tuesday, April 22, 14

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

    here end activesupport Tuesday, April 22, 14
  84. • Regexp on Notifications.subscribe • Event#parent_of? • Temporary subscription •

    Unsubscribe • LogSubscriber 69 activesupport Tuesday, April 22, 14
  85. Where Are We Going? The Pattern Coupling Mechanics Responsibilities 70

    Tuesday, April 22, 14
  86. http://flic.kr/p/a1dEec Notifiers Tuesday, April 22, 14

  87. Naming 72 Tuesday, April 22, 14

  88. Flexible Payload 73 Tuesday, April 22, 14

  89. Primitives at Boundaries 74 Tuesday, April 22, 14

  90. 75 http://flic.kr/p/8w9PyF Subscribers Tuesday, April 22, 14

  91. Who subscribes? 76 Tuesday, April 22, 14

  92. 77 Tuesday, April 22, 14

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

    NewsletterSubscriptions.create(user.id) UserMailer.async(:deliver_welcome, user.id) user end end Tuesday, April 22, 14
  94. 79 Nesting Tuesday, April 22, 14

  95. Synchronous 80 Tuesday, April 22, 14

  96. 81 class SomeSubscriber def initialize events.subscribe(:configured) do log_config end end

    def log_config ... end end Leaks Tuesday, April 22, 14
  97. Where Are We Going? The Pattern Coupling Mechanics Responsibilities 82

    Tuesday, April 22, 14
  98. The Pattern Coupling Mechanics Responsibilities ? Jason Clark @jasonrclark Ruby

    Agent Engineer Tuesday, April 22, 14