Events

Raindeer is an event-driven framework that represents the Request-Response lifecycle as events. It's easy to latch on to any event as they happen and perform additional tasks.

Note

Raindeer is event-driven internally but your application doesn't have to be. In fact, Raindeer's main events are abstracted away to such a degree that you can ignore them.

Request-Response Lifecycle

  1. RequestEvent - The server converts HTTP requests into request events
  2. RouteEvent - The router creates route events from request events
  3. RenderEvent - A node observes a route event and renders a response
  4. ResponseEvent - A response event is converted into an HTTP response and given to the requester

Observing Events

observe syntax

Observe the event with:

class MyNode < LowNode
  observe MyEvent

  def render
    "My Response"
  end
end

on syntax [UNRELEASED]

Better distinguish event handlers from regular methods in a class with:

on MyEvent do |my_event|
  # Do something.
end

Observe a particular action:

on MyEvent => :my_action do |my_event|
  # Do something.
end

Breakdown:

See also: Observers

Events Types

RequestEvent

The RequestEvent contains a request attribute, which is a Protocol::HTTP::Request provided by Protocol::HTTP. It is also responsible for keeping track of every subsequent event.

Ordering Observers

Say you want to authenticate, log or redirect before every request, then RequestEvent.define is the answer. We do it this way to minimise per-request overhead; if you're being DDoS'd then why give them the satisfaction or processing a nice tasty RouteEvent for them before denying their request?

Low::Events::RequestEvent.define do |observers|
  observers = [MyLogger, MyAuthenticator, *observers]
end

These new observers will receive a :request action, so add a request method to these classes and return a LowNode.render(event:), a ResponseEvent or nil.

Tip

Event observers should only be redefined once, so keep them in a central location. Example: app/events/request_event

RouteEvent

Observe a route and it will trigger the render action/class method on a node/observer. The method factory pattern takes over; instantiating the node with a RenderEvent, before calling the instance render method with the same RenderEvent.

RenderEvent

Calls render instance method by defalult, after observing a route or rendering a node via Antlers in the template.

ResponseEvent

Attribute response is a Protocol::HTTP::Response

Advanced

Creating Events

Note

Creating events is completely optional.

Milestone events for the main flows are created for you and you can just listen to them. However you may want to create your own:

class MyEvent < LowEvent
  attr_reader :my_data

  def initialize(my_data:, action: :my_action)
    super(key: self.class, action:)

    @my_data = my_data
  end
end

Breakdown:

Trigger the event's action on its observers with:

MyEvent.trigger(my_data: "Custom Data")

Architecture

Event-Command Hybrid

Raindeer events are different to traditional events in event-driven architectures; they represent something that is currently happening, not something that has already happened.

Event LowEvent Command
Tense Past Past/Present/Future Future
Naming OrderCreated OrderEvent CreateOrder
Action 🚫 :create_order ✅
Mutability Immutable Mutable/Immutable Immutable
State 🚫 Ordered Actions 🚫
Broadcasting One to many Ordered one to many One to one
Return 🚫 Value or nil Value

Event Tree

Because events represent a period of time they will have sub-events, resulting in a tree-like structure of parent and child events:

Event Tree

This structure can be used in debugging for enhanced observability. In the future this information will be shown in the /system UI, detailing who triggered and who responded to an event.

Problems VS Solutions

Event-driven and distributed systems can suffer from a "who did what" problem. Raindeer mitigates each of these pain points:

Problem Solution
Implicit/hidden wiring Explicit observe/observers <<
Unpredictible ordering Ordered and one-directional flow
Unpredictible effects trigger and take action types
Testing side effects Observers must be added per test
Discoverability System UI lists events/routes/observers
Debugging and Logging Events are pipelines and tracked via UI

Unit Testing

Events must be manually observed in the test if you want to trigger observers.