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.

Jason R Clark

April 22, 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
    Tuesday, April 22, 14

    View Slide

  2. Storytime!
    Tuesday, April 22, 14

    View Slide

  3. Tuesday, April 22, 14

    View Slide

  4. Tuesday, April 22, 14

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  11. https://flic.kr/p/6qdTC1
    Tuesday, April 22, 14

    View Slide

  12. Where Are We Going?
    The Pattern
    Coupling
    Mechanics
    Responsibilities
    12
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  14. 14
    Tuesday, April 22, 14

    View Slide

  15. 15
    Tuesday, April 22, 14

    View Slide

  16. 15
    Tuesday, April 22, 14

    View Slide

  17. 15
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  19. 17
    Tuesday, April 22, 14

    View Slide

  20. 17
    Notifier
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  30. Where Are We Going?
    The Pattern
    Coupling
    Mechanics
    Responsibilities
    22
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  32. newrelic_rpm
    24
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  46. 34
    def finish_setup(config)
    events.notify(:configured)
    end
    newrelic_rpm
    Tuesday, April 22, 14

    View Slide

  47. 35
    class Beacon
    def initialize
    events.subscribe(:configured) do
    Net::HTTP.get("http://...")
    end
    end
    end
    newrelic_rpm
    Tuesday, April 22, 14

    View Slide

  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

    View Slide

  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

    View Slide

  50. More Focused
    Tests
    38
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  52. Domain Language
    40
    Tuesday, April 22, 14

    View Slide

  53. Implicit Ordering
    41
    Tuesday, April 22, 14

    View Slide

  54. Debugging
    42
    Tuesday, April 22, 14

    View Slide

  55. Performance
    43
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  57. 45
    newrelic_rpm
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  63. 48
    class CrossAppTracing
    def initialize
    events.subscribe(:before_call) do |e|
    process_request(e)
    end
    ...
    end
    end
    newrelic_rpm
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  65. Looser Coupling
    50
    Tuesday, April 22, 14

    View Slide

  66. Compatibilty
    Layer
    51
    Tuesday, April 22, 14

    View Slide

  67. Where Are We Going?
    The Pattern
    Coupling
    Mechanics
    Responsibilities
    52
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide

  70. 55
    class SimpleEvents::Notifier
    def initialize
    @events = {}
    ...
    end
    end
    simple_events
    Tuesday, April 22, 14

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  77. ActiveSupport::
    Notifications
    http://flic.kr/p/aWNwU2
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  82. 67
    AS::Notifications.instrument("evt", :data => 1)
    activesupport
    Tuesday, April 22, 14

    View Slide

  83. 68
    AS::Notifications.instrument("evt", :data => 1) do
    # your timed code here
    end
    activesupport
    Tuesday, April 22, 14

    View Slide

  84. • Regexp on Notifications.subscribe
    • Event#parent_of?
    • Temporary subscription
    • Unsubscribe
    • LogSubscriber
    69
    activesupport
    Tuesday, April 22, 14

    View Slide

  85. Where Are We Going?
    The Pattern
    Coupling
    Mechanics
    Responsibilities
    70
    Tuesday, April 22, 14

    View Slide

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

    View Slide

  87. Naming
    72
    Tuesday, April 22, 14

    View Slide

  88. Flexible Payload
    73
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  92. 77
    Tuesday, April 22, 14

    View Slide

  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

    View Slide

  94. 79
    Nesting
    Tuesday, April 22, 14

    View Slide

  95. Synchronous
    80
    Tuesday, April 22, 14

    View Slide

  96. 81
    class SomeSubscriber
    def initialize
    events.subscribe(:configured) do
    log_config
    end
    end
    def log_config
    ...
    end
    end
    Leaks
    Tuesday, April 22, 14

    View Slide

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

    View Slide

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

    View Slide