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
    Posts[:title, :body] + Users[:username]
  end
end

This data expression follows the posts table's user_id foreign key to the users table, generating this SQL:

SELECT title, body, user_id FROM posts
SELECT username, id FROM users WHERE (id IN (...))

...which results in a list of posts with each post's user nested inside it:

[
  { title: 'Hello', body: '...', user: { username: 'alice' } },
  { title: 'World', body: '...', user: { username: 'bob' } }
]