pub trait Module {
// Required method
fn setup(&self, ctx: &mut Context) -> Result;
}Expand description
Module.
It’s modules all the way down – the fundamental idea of this library is that all specific functionality can and should be provided as modules, resulting in an inherently flexible and composable system, where users can pick and choose each module according to their requirements.
§Architecture
This crate is carefully integrated with other crates of this library, most
notably the zrx-id and zrx-stream crates.
-
Each module is built from a dedicated
Workflow, which represents a set ofStreamtransformations. In workflows, streams can be combined and transformed using operators likeStream::join,Stream::mapand many others, a concept borrowed from reactive programming. -
Modules can be attached and detached at runtime, allowing users to easily add and remove functionality as required. The scheduler, which takes care of orchestration, automatically recomputes the execution plan whenever modules are added or removed.
-
Modules can co-operate through typed subscriptions. Within a module, typed subscriptions can be created through
Context::add, adding a sourceStreamto the module’s workflow that automatically subscribes to matching streams from other modules.
§Considerations
Creating a module system that stands the test of time is a non-trivial task. Deliberate care must be taken to ensure that the system isn’t only flexible and extensible enough, but also that it can evolve without major breaking changes. The following design decisions ensure just that:
-
The API surface of the module system is intentionally minimal, made of a single entrypoint,
Module::setup. As cooperation and synchronization of modules is implemented with graphs, a single entrypoint is sufficient for all use cases, and allows to keep the API simple and extensible. -
Upon initialization, modules are passed a
Contextfor all interactions with the system, which allows us to add new functionality without breaking changes, as long as the signature of existing functions stays the same. It also means experimental features can be gated behind feature flags. -
Modules are not generic over identifier types, like large parts of this library, but tied to
Id. The reason is that thezrx-idcrate is the canonical way of working with identifiers, and the module system is designed to be tightly integrated with it.
Our design is fundamentally different from some plugin and module systems that expose a limited number of extension points (often called hooks), each of which requires a static signature. This makes it much harder to evolve without breaking the entire ecosystem. By contrast, our design doesn’t come with a fixed number of extension points, but allows users to create their own extension points through subscriptions.