Skip to main content

rainier_queue/
lib.rs

1//! # rainier-queue
2//!
3//! Deferred work: the [`Job`] contract, the [`Queue`] port and its drivers, and
4//! the [`Worker`] that runs them.
5//!
6//! ```
7//! use rainier_queue::{Job, JobContext, JobRegistry, MemoryQueue, Queue, QueueManager};
8//! use serde::{Deserialize, Serialize};
9//! use std::sync::Arc;
10//!
11//! #[derive(Serialize, Deserialize)]
12//! struct SendWelcomeEmail { user_id: u64 }
13//!
14//! #[async_trait::async_trait]
15//! impl Job for SendWelcomeEmail {
16//!     const NAME: &'static str = "mail.welcome";
17//!     const QUEUE: &'static str = "mail";
18//!
19//!     async fn handle(&self, _context: &JobContext) -> rainier_support::Result<()> {
20//!         Ok(())
21//!     }
22//! }
23//!
24//! # #[tokio::main] async fn main() -> rainier_support::Result<()> {
25//! let registry = Arc::new(JobRegistry::new().with::<SendWelcomeEmail>());
26//! let queue = Arc::new(MemoryQueue::new());
27//! let dispatcher = QueueManager::new(queue.clone(), registry);
28//!
29//! dispatcher.dispatch(SendWelcomeEmail { user_id: 1 }).await?;
30//! assert_eq!(queue.size("mail").await?, 1);
31//! # Ok(()) }
32//! ```
33//!
34//! ## A job crosses a process boundary
35//!
36//! Everything about the design follows from that. A job is written by a web
37//! request and read, later, by a worker that may be a different process on a
38//! different machine — so a job is a **serialisable payload plus a stable
39//! name**, not a closure, and the worker needs a [`JobRegistry`] to turn the
40//! name back into code. Dependencies cannot be captured either, so a running
41//! job resolves them from the container through its [`JobContext`].
42//!
43//! ## Drivers
44//!
45//! | Driver | Survives a restart | For |
46//! |---|---|---|
47//! | [`SyncQueue`] | — runs inline | development, and tests that want the side effect |
48//! | [`MemoryQueue`] | no | tests, single-process development |
49//! | [`DatabaseQueue`] | yes | production, on the database you already have |
50//! | `SqsQueue` | yes | production, managed — needs the `sqs` feature |
51//!
52//! ## Testing
53//!
54//! [`QueueManager::fake`] records dispatches instead of performing them:
55//!
56//! ```
57//! # use rainier_queue::{Job, JobContext, QueueManager};
58//! # use serde::{Deserialize, Serialize};
59//! # #[derive(Serialize, Deserialize)] struct SendInvoice;
60//! # #[async_trait::async_trait] impl Job for SendInvoice {
61//! #     const NAME: &'static str = "billing.invoice";
62//! #     async fn handle(&self, _: &JobContext) -> rainier_support::Result<()> { Ok(()) }
63//! # }
64//! # #[tokio::main] async fn main() -> rainier_support::Result<()> {
65//! let queue = QueueManager::fake();
66//! queue.dispatch(SendInvoice).await?;
67//! queue.assert_pushed_times::<SendInvoice>(1);
68//! # Ok(()) }
69//! ```
70
71#![deny(missing_docs)]
72#![deny(rustdoc::broken_intra_doc_links)]
73
74pub mod database;
75pub mod driver;
76pub mod job;
77pub mod manager;
78pub mod queue;
79#[cfg(feature = "redis")]
80pub mod redis;
81#[cfg(feature = "sqs")]
82pub mod sqs;
83pub mod worker;
84
85pub use database::{DatabaseQueue, FailedJobRow, JobRow};
86pub use driver::QueueDriver;
87pub use job::{Job, JobContext, JobRegistry, QueuedJob};
88pub use manager::{PendingDispatch, QueueManager, SyncQueue};
89pub use queue::{FailedJob, MemoryQueue, Queue};
90#[cfg(feature = "redis")]
91pub use redis::{Keys as RedisQueueKeys, RedisQueue};
92#[cfg(feature = "sqs")]
93pub use sqs::SqsQueue;
94pub use worker::{
95    JobFailed, JobProcessed, JobProcessing, JobReleased, Outcome, Worker, WorkerOptions,
96    WorkerStats,
97};
98
99// Re-exported so job implementations get the attribute macro without adding
100// the dependency themselves.
101pub use async_trait::async_trait;