Skip to content

Commit

Permalink
Adds observability to the new event bus
Browse files Browse the repository at this point in the history
This commit adds observability abilities to the new event bus adapter:

- It adds creation time and caller location to the `Spree::Event::Event`
  instance bound to the subscribers.
- It wraps the execution of listeners within a new
  `Spree::Event::Execution` class, which wraps the execution time, a
  benchmark of the operation, and the result returned by the
  subscription.

Both the `Spree::Event::Event` and the list of `Spree::Event::Execution`
are wrapped within a new `Spree::Event::Firing` object, which is
returned on `Spree::Event.fire`. Therefore, a typical flow could be
getting the event's caller from within the subscriber block and then
inspecting the firing object from there if needed.

Having all the metadata collected in a single place when we do
`Spree::Event.fire` will simplify things if we implement some event
store in the future.
  • Loading branch information
waiting-for-dev committed Nov 26, 2021
1 parent f928f1a commit d4d3c0c
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 202 deletions.
9 changes: 7 additions & 2 deletions core/lib/spree/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ module Event
# instead.
# @option opts [Any] :adapter Reserved to indicate the adapter to use as
# event bus. Defaults to {#default_adapter}
# @return [Spree::Event::Event] an event object, unless the adapter is
# @return [Spree::Event::Firing] A firing object encapsulating metadata for
# the event and the originated listener executions, unless the adapter is
# {Spree::Event::Adapters::ActiveSupportNotifications}
#
# @example Trigger an event named 'order_finalized'
Expand All @@ -71,7 +72,11 @@ module Event
def fire(event_name, opts = {}, &block)
adapter = opts.delete(:adapter) || default_adapter
handle_block_on_fire(block, opts, adapter) if block_given?
adapter.fire normalize_name(event_name), opts
if deprecation_handler.legacy_adapter?(adapter)
adapter.fire(normalize_name(event_name), opts)
else
adapter.fire(normalize_name(event_name), caller_location: caller_locations(1)[0], **opts)
end
end

# Subscribe to events matching the given name.
Expand Down
11 changes: 6 additions & 5 deletions core/lib/spree/event/adapters/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'spree/event/event'
require 'spree/event/listener'
require 'spree/event/firing'

module Spree
module Event
Expand Down Expand Up @@ -32,12 +33,12 @@ def initialize
end

# @api private
def fire(event_name, opts = {})
Event.new(payload: opts).tap do |event|
listeners_for_event(event_name).each do |listener|
listener.call(event)
end
def fire(event_name, caller_location: caller_locations(1)[0], **payload)
event = Event.new(payload: payload, caller_location: caller_location)
executions = listeners_for_event(event_name).map do |listener|
listener.call(event)
end
Firing.new(event: event, executions: executions)
end

# @api private
Expand Down
21 changes: 20 additions & 1 deletion core/lib/spree/event/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,34 @@ module Event
# Spree::Event.subscribe 'event_name' do |event|
# puts event.payload['foo'] #=> 'bar'
# end
#
# Besides, it can be accessed through the returned value in {Spree::Event.fire}.
# It can be useful for debugging and logging purposes, as it contains
# helpful metadata like the event time or the caller location.
class Event
# Hash with the options given to {Spree::Event.fire}
#
# @return [Hash]
attr_reader :payload

# Time of the event firing
#
# @return [Time]
attr_reader :firing_time

# Location for the event caller
#
# It's usually set by {Spree::Event.fire}, and it points to the caller of
# that method.
#
# @return [Thread::Backtrace::Location]
attr_reader :caller_location

# @api private
def initialize(payload:)
def initialize(payload:, caller_location:, firing_time: Time.now.utc)
@payload = payload
@caller_location = caller_location
@firing_time = firing_time
end
end
end
Expand Down
44 changes: 44 additions & 0 deletions core/lib/spree/event/execution.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Spree
module Event
# Execution of a {Spree::Event::Listener}
#
# When an event is fired, it executes all subscribed listeners. Every single
# execution is represented as an instance of this class. It contains the
# result value of the listener, along with helpful metadata as the time of
# the execution or a benchmark for it.
#
# You'll most likely interact with this class for debugging or logging
# purposes through the returned value in {Spree::Event.fire}.
class Execution
# The listener to which the execution belongs
#
# @return [Spree::Event::Listener]
attr_reader :listener

# The value returned by the {#listener}'s block
#
# @return [Any]
attr_reader :result

# Benchmark for the {#listener}'s block
#
# @return [Benchmark::Tms]
attr_reader :benchmark

# Time of execution
#
# @return [Time]
attr_reader :execution_time

# @private
def initialize(listener:, result:, benchmark:, execution_time: Time.now.utc)
@listener = listener
@result = result
@benchmark = benchmark
@execution_time = execution_time
end
end
end
end
30 changes: 30 additions & 0 deletions core/lib/spree/event/firing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module Spree
module Event
# The result of firing an event
#
# It encapsulates a fired {Spree::Event::Event} as well as the
# {Spree::Event::Execution}s it originated.
#
# This class is useful mainly for debugging and logging purposes. An
# instance of it is returned on {Spree::Event.fire}.
class Firing
# Fired event
#
# @return [Spree::Event::Event]
attr_reader :event

# Listener executions that the firing originated
#
# @return [Array<Spree::Event::Execution>]
attr_reader :executions

# @api private
def initialize(event:, executions:)
@event = event
@executions = executions
end
end
end
end
10 changes: 9 additions & 1 deletion core/lib/spree/event/listener.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# frozen_string_literal: true

require 'benchmark'
require 'spree/event/execution'

module Spree
module Event
# Subscription to an event
Expand All @@ -25,7 +28,12 @@ def initialize(pattern:, block:)

# @api private
def call(event)
@block.call(event)
result = nil
benchmark = Benchmark.measure do
result = @block.call(event)
end

Execution.new(listener: self, result: result, benchmark: benchmark)
end

# @api private
Expand Down
Loading

0 comments on commit d4d3c0c

Please sign in to comment.