Architecture

Raindeer Architecture

Design Patterns

Event-driven

Raindeer is event-driven, but as much as possible events are created and triggered by the framework itself, hiding the complexity of traditional event-driven applications in order to make the framework easier to use.

Asynchronous

Raindeer responds to requests asynchronously via Fibers. This improves performance and concurrency on IO-bound workloads like getting data from a database. This also means that you need to block the current Fiber when debugging.

Parallelism

While the handling of thousands of requests is asynchronous, each individual request can have its workload parallelised. Inside each LowNode are various tools to achieve parallelism.

Pipelines

Pipelines are one of the simplest and easiest patterns to understand. What's not to love about a linear series of events? In Raindeer everything's a pipeline:

Composition

Aspect-orientated programming

Abstract-orientated programming is larger in scope than what Raindeer makes use of. Primarily Raindeer uses AOP in the sense that internally there are "filters" that run before milestone events, creating and triggering events on your behalf.

Namespaces

For example, when you call Raindeer.router it calls Providers['rain.router'] and this dependency is an instance of Rain::Router.

In summary:

Philosophy

🪆 Composition over convention

Methods and classes should be compositional, so that you can understand their hidden complexity by drilling down into them as they go, rather than calling one magic method that does a bunch of side quests. APIs should be less magical and more compositional.

A perfect example is the has_many helper method provided by various ORMs. This method adds "association" methods to a model, then hides the fact that databases do joins on tables. You will have to do a JOIN on a table eventually, so it's better to represent tables as being merged together from the start. There has to be a more compositional way that exposes the database structure while letting you query that structure easily. Raindeer uses Data Expressions to represent table structure compositionally:

class PostsData < LowData
  def all
    Users[:username] + Posts[:title, :body]
  end
end

This data expression generates SQL to OUTER JOIN the user table with the posts table and results in a list of posts with the user's username included in each row.