Wiring
RedisModule, RedisQueueModule and RedisWorkerModule — how producer-only and worker apps share a features crate without spawning workers they don't run.
The same features::audio crate compiles into the API binary and the
worker binary. Only the worker drains the queue. That split is two
module imports, two mains, one shared features crate — the framework
decides which #[process] methods actually run by walking the access
graph from each app’s root.
Three modules, three roles
Section titled “Three modules, three roles”| Module | Role | Imported by |
|---|---|---|
RedisModule::for_root(cfg) | Opens the one Redis connection every Redis binding shares — the queue producer, the worker, the rate-limit store. | Every app that touches Redis. |
RedisQueueModule | Binds the producer over that connection: Arc<dyn JobProducer>, the portable handle a feature injects to enqueue. | Every app that enqueues. |
RedisWorkerModule::for_root(cfg) | Contributes the RedisWorker transport — drains #[process] methods at boot and spawns one worker each; its own settings are NESTRS_REDIS__WORKER__* (the drain window at shutdown). | Worker apps only. |
RedisModule::for_root(None) reads NESTRS_REDIS__URL from the environment;
RedisModule::for_root(RedisConfig { url, .. }) supplies that URL as the
base the environment overlays — so a deployment setting NESTRS_REDIS__URL
still wins, field by field (the dual-path rule).
One connection, one variable, named for the crate that parses it: a
throttler-only app sets the same NESTRS_REDIS__URL. See fundamentals/modules for the
four boot phases that resolve the connection before any transport attaches.
The worker app
Section titled “The worker app”The worker imports the substrate, both queue bindings and the feature’s queue
adapter. That’s it — AudioQueueModule pulls in the AudioModule port itself.
use nest_rs::config::ConfigModule;use nest_rs::core::module;use nest_rs::redis::{RedisModule, RedisQueueModule, RedisWorkerModule};
use features::audio::AudioQueueModule;
#[module(imports = [ ConfigModule::for_root(), RedisModule::for_root(None), RedisQueueModule, RedisWorkerModule::for_root(None), AudioQueueModule,])]pub struct WorkerModule;At boot:
ConfigModulereads env, materializesRedisConfigfromNESTRS_REDIS__URL.RedisModule::for_root(None)registers a factory that opens theRedisConnection.RedisQueueModuleregisters a factory — declared to run after the connection’s — that bindsRedisQueueProducerasArc<dyn JobProducer>.RedisWorkerModule::for_root(None)contributes theRedisWorkertransport.AudioQueueModulebringsAudioProcessor(the#[processor]host) and its#[process]methods into the reachable set.RedisWorker::configuredrainsinventory::iter::<ProcessMethod>(), keeps only the methods whose provider is reachable, and logs each one.serveruns one queue runtime draining every method’s queue, one job at a time per queue.
The producer-only app
Section titled “The producer-only app”A producer-only binary imports RedisModule::for_root(...) and
RedisQueueModule, and skips RedisWorkerModule entirely. It gets
Arc<dyn JobProducer> to push with — and no worker spawn, no drained
inventory, no queue runtime.
use nest_rs::config::ConfigModule;use nest_rs::core::module;use nest_rs::http::HttpModule;use nest_rs::redis::{RedisModule, RedisQueueModule};use nest_rs::seaorm::{SeaOrmDatabaseModule, SeaOrmModule};
use features::audio::{AudioHttpModule, AudioScheduleModule};
#[module(imports = [ ConfigModule::for_root(), HttpModule::for_root(None), SeaOrmModule::for_root(None), SeaOrmDatabaseModule, RedisModule::for_root(None), RedisQueueModule, AudioHttpModule, AudioScheduleModule,])]pub struct ApiModule;That apps/api is exactly this shape: producer-only. Its HTTP
controller and scheduled timer both call AudioService::enqueue_transcode,
which pushes through the bound JobProducer onto Redis. No worker
runs in this process — that’s the worker binary’s job.
Module-gating across the shared crate
Section titled “Module-gating across the shared crate”The worker depends on the shared features crate, so AudioController
and AudioResolver are compiled in. They are not active: the
worker doesn’t import AudioHttpModule or AudioGraphqlModule, so
the access graph never marks those providers as reachable. Every
transport — HttpTransport, RedisWorker, GraphqlTransport — filters
its inventory through the ReachableProviders set the access graph
computes from the running app’s root. Linked but unreachable ⇒ inert,
with a boot tracing::warn if the discovery layer expected them to
appear.
The same property cuts the other way: the API binary’s
AudioQueueModule is not imported, so its #[process] methods
sit in the binary as dead code. No worker is spawned for audio in
the API. Even if you accidentally imported RedisWorkerModule in the
API, omitting AudioQueueModule would keep AudioProcessor::transcode
out of the reachable set — RedisWorker::serve would idle until
shutdown.
This is what makes splitting producer and consumer across deploys
cheap. The features crate is one library, the binaries are
compositions of it, and module-gating is the line that decides what
each binary actually does at runtime. See
fundamentals/modules for the access graph
that enforces this — boot fails with AccessGraphError if a provider
injects something its module doesn’t own or import transitively.
What the wire looks like at boot
Section titled “What the wire looks like at boot”$ nestrs run dev worker INFO nest_rs::app: attached module-contributed transport transport="RedisWorker" INFO nest_rs::queue: registered queue processor processor="AudioProcessor::transcode" queue="audio" retries=3One attached line per transport, one registered line per
discovered method that survived the reachability filter. The
producer-only API’s boot opens the same connection, but logs no attached transport=RedisWorker and no registered queue processor lines — the worker
side never wakes up.
Reference
Section titled “Reference”crates/nest-rs-redis/src/module.rs—RedisModule,RedisSetup::collect(config resolution + the one connection).crates/nest-rs-redis/src/queue/module.rs—RedisQueueModulebindingRedisQueueProducerasdyn JobProducerover it.crates/nest-rs-redis/src/worker/module.rs—RedisWorkerModulecontributing theRedisWorkertransport.crates/nest-rs-redis/src/worker/consumer.rs—RedisWorker::configurefilteringProcessMethodbyReachableProviders.apps/api/,apps/worker/— producer-only and worker shapes side by side.- fundamentals/modules — boot phases, imports, dynamic modules, the access graph.
Going further
Section titled “Going further”- Producing jobs — enqueue from a service.
- Retries and failure — the worker-side failure budget.
- Observability — the boot and per-job lines this wiring emits.