Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Evented Rails - Decoupling complex domains in Rails with Domain Events
# -----------------------------
# Passenger (Experience) Domain
# -----------------------------
module Passenger
# app/domains/passenger/find_driver_match.rb
class FindDriverMatch
include Wisper::Publisher
def call(passenger, ride_requirements)
# Publishes the event (a fact), but lets someone else do the rest of the hard work.
broadcast("passenger_ride_requested", {
ride_requirements: ride_requirements,
passenger: passenger
})
end
end
# A class devoted to handling incoming events from the Routing domain.
# Handlers are added to specific events as-needed from within this (Passenger) domain.
#
# app/domains/passenger/routing_event_handler.rb
class RoutingEventHandler
def self.routing_driver_notified
# Inform the passenger that the driver is on their way
Passenger::SendDriverArrivalNotification.new.call(passenger, driver)
end
def self.routing_no_driver_found
# Inform the passenger that no driver is available at this time.
Passenger::SendRideUnavailableNotification.new.call(passenger)
end
end
end
# --------------
# Routing Domain
# --------------
module Routing
# A service class devoted to sending a notification or text to the driver and
# acknowledging that they are on their way to the passenger.
#
# app/domains/routing/notify_driver.rb
class NotifyDriver
include Wisper::Publisher
def call(driver, passenger)
# ...
# Perform some Routing related code that notifies the driver's mobile device
# and sends them on their way
# ...
broadcast("routing_driver_notified", driver: driver, passenger: passenger)
end
end
# Lightweight notification class that broadcasts the event that a driver is not available.
class NoDriverFound
include Wisper::Publisher
def call(passenger)
broadcast("routing_no_driver_found", passenger: passenger)
end
end
# A class devoted to handling incoming events from the Passenger domain.
# Handlers are added to specific events as-needed from within this (Routing) domain.
#
# app/domains/routing/passenger_event_handler.rb
class PassengerEventHandler
# Handles the event appropriately with domain-specific code.
# The method name here corresponds with the name of the event.
def self.passenger_ride_requested(payload)
driver = Routing::FindDrivers.new(payload[:passenger]).call
.select { |d| d.meets?(payload[:ride_requirements]) && d.matches_some_deeply_specific_thing? }
.sample
if driver
Routing::NotifyDriver.new.call(driver, payload[:passenger])
else
Routing::NoDriverFound.new.call(payload[:passenger])
end
end
end
end
# The app is configured to hook up a subscriber to the "passenger_experience_ride_requested" event
# config/initializers/domain_event_subscriptions.rb
Wisper.subscribe(Passenger::FindDriverMatch, scope: Routing::PassengerEventHandler)
Wisper.subscribe(Routing::NotifyDriver, scope: Passenger::RoutingEventHandler)
module Passenger
class FindDriverMatch
def call(passenger, ride_requirements)
# Call another part of the system that has to do with routing
# but requires deep internal knowledge of that code!
driver = Routing::FindDrivers.new(passenger).call
.select! { |d| d.meets?(ride_requirements) && d.matches_some_deeply_specific_thing? }
.sample
if driver
Routing::NotifyDriver.new.call(driver)
# Inform the passenger that the driver is on their way
Passenger::SendDriverArrivalNotification.new.call(passenger, driver)
else
# Inform the passenger that no driver is available at this time.
Passenger::SendRideUnavailableNotification.new.call(passenger)
end
end
end
end
# The app is configured to hook up a subscriber to the
# "passenger_experience_ride_requested" event
# config/initializers/domain_event_subscriptions.rb
Wisper.subscribe(
Passenger::RideRequested,
scope: Routing::PassengerEventHandler,
async: true
)
Wisper.subscribe(
Routing::NotifyDriver,
scope: Passenger::RoutingEventHandler
async: true
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment