qml_rs/lib.rs
1//! # qml-rs
2//!
3//! A production-ready Rust implementation of background job processing.
4//!
5//! **qml** provides a complete, enterprise-grade background job processing solution
6//! with multiple storage backends, multi-threaded processing, race condition prevention,
7//! and real-time monitoring capabilities.
8//!
9//! ## ๐ **Production Ready Features**
10//!
11//! - **3 Storage Backends**: Memory, Redis, PostgreSQL with atomic operations
12//! - **Multi-threaded Processing**: Configurable worker pools with job scheduling
13//! - **Web Dashboard**: Real-time monitoring with WebSocket updates
14//! - **Race Condition Prevention**: Comprehensive locking across all backends
15//! - **Job Lifecycle Management**: Complete state tracking and retry logic
16//! - **Production Deployment**: Docker, Kubernetes, and clustering support
17//!
18//! ## ๐ฏ **Storage Backends**
19//!
20//! ### Memory Storage (Development/Testing)
21//! ```rust
22//! use qml_rs::{MemoryStorage, Job, Storage};
23//! use std::sync::Arc;
24//!
25//! # tokio_test::block_on(async {
26//! let storage = Arc::new(MemoryStorage::new());
27//! let job = Job::new("process_data", vec!["input.csv".to_string()]);
28//! storage.enqueue(&job).await.unwrap();
29//! # });
30//! ```
31//!
32//! ### Redis Storage (Distributed/High-Traffic)
33//! ```rust,ignore
34//! #[cfg(feature = "redis")]
35//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
36//! use qml_rs::storage::{RedisConfig, StorageInstance};
37//! use std::time::Duration;
38//! let config = RedisConfig::new()
39//! .with_url("redis://localhost:6379")
40//! .with_pool_size(20)
41//! .with_command_timeout(Duration::from_secs(5));
42//!
43//! let storage = StorageInstance::redis(config).await?;
44//! Ok(())
45//! }
46//! ```
47//!
48//! ### PostgreSQL Storage (Enterprise/ACID)
49//! ```rust,ignore
50//! #[cfg(feature = "postgres")]
51//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
52//! use qml_rs::storage::{PostgresConfig, StorageInstance};
53//! use std::sync::Arc;
54//! let config = PostgresConfig::new()
55//! .with_database_url("postgresql://user:pass@localhost:5432/qml")
56//! .with_auto_migrate(true)
57//! .with_max_connections(50);
58//!
59//! let storage = StorageInstance::postgres(config).await?;
60//! Ok(())
61//! }
62//! ```
63//!
64//! ## โก **Job Processing Engine**
65//!
66//! ### Basic Worker Implementation
67//! ```rust
68//! use qml_rs::{Worker, Job, WorkerContext, WorkerResult, QmlError};
69//! use async_trait::async_trait;
70//!
71//! struct EmailWorker;
72//!
73//! #[async_trait]
74//! impl Worker for EmailWorker {
75//! async fn execute(&self, job: &Job, _context: &WorkerContext) -> Result<WorkerResult, QmlError> {
76//! let email = &job.arguments[0];
77//! println!("Sending email to: {}", email);
78//! // Email sending logic here
79//! Ok(WorkerResult::success(None, 0))
80//! }
81//!
82//! fn method_name(&self) -> &str {
83//! "send_email"
84//! }
85//! }
86//! ```
87//!
88//! ### Complete Job Server Setup
89//! ```rust
90//! use qml_rs::{
91//! BackgroundJobServer, MemoryStorage, ServerConfig,
92//! WorkerRegistry, Job
93//! };
94//! use std::sync::Arc;
95//!
96//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
97//! // Setup storage and registry
98//! let storage = Arc::new(MemoryStorage::new());
99//! let mut registry = WorkerRegistry::new();
100//! // registry.register(Box::new(EmailWorker)); // Add your workers
101//!
102//! // Configure server
103//! let config = ServerConfig::new("server-1")
104//! .worker_count(4)
105//! .queues(vec!["critical".to_string(), "normal".to_string()]);
106//!
107//! // Start job server
108//! let server = BackgroundJobServer::new(config, Arc::new(MemoryStorage::new()), Arc::new(registry));
109//! // server.start().await?; // Start processing
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! ## ๐ **Dashboard & Monitoring**
115//!
116//! ### Real-time Web Dashboard
117//! ```rust
118//! use qml_rs::{DashboardServer, MemoryStorage};
119//! use std::sync::Arc;
120//!
121//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
122//! let storage = Arc::new(MemoryStorage::new());
123//! let dashboard = DashboardServer::new(storage, Default::default());
124//!
125//! // Start dashboard on http://localhost:8080
126//! // dashboard.start("0.0.0.0:8080").await?;
127//! # Ok(())
128//! # }
129//! ```
130//!
131//! The dashboard provides:
132//! - **Real-time Statistics**: Job counts by state, throughput metrics
133//! - **Job Management**: View, retry, delete jobs through web UI
134//! - **WebSocket Updates**: Live updates without page refresh
135//! - **Queue Monitoring**: Per-queue statistics and performance
136//!
137//! ## ๐ **Race Condition Prevention**
138//!
139//! All storage backends implement atomic job fetching to prevent race conditions:
140//!
141//! - **PostgreSQL**: `SELECT FOR UPDATE SKIP LOCKED` with dedicated lock table
142//! - **Redis**: Lua scripts for atomic operations with distributed locking
143//! - **Memory**: Mutex-based locking with automatic cleanup
144//!
145//! ```rust
146//! use qml_rs::{Storage, MemoryStorage};
147//!
148//! # tokio_test::block_on(async {
149//! let storage = MemoryStorage::new();
150//!
151//! // Atomic job fetching - prevents multiple workers from processing same job
152//! let job = storage.fetch_and_lock_job("worker-1", None).await.unwrap();
153//! match job {
154//! Some(job) => println!("Got exclusive lock on job: {}", job.id),
155//! None => println!("No jobs available"),
156//! }
157//! # });
158//! ```
159//!
160//! ## ๐ **Job States & Lifecycle**
161//!
162//! Jobs progress through well-defined states:
163//!
164//! ```text
165//! Enqueued โ Processing โ Succeeded
166//! โ โ
167//! Scheduled Failed โ AwaitingRetry โ Enqueued
168//! โ โ
169//! Deleted Deleted
170//! ```
171//!
172//! ### State Management Example
173//! ```rust
174//! use qml_rs::{Job, JobState};
175//!
176//! let mut job = Job::new("process_payment", vec!["order_123".to_string()]);
177//!
178//! // Job starts as Enqueued
179//! assert!(matches!(job.state, JobState::Enqueued { .. }));
180//!
181//! // Transition to Processing
182//! job.set_state(JobState::processing("worker-1", "server-1")).unwrap();
183//!
184//! // Complete successfully
185//! job.set_state(JobState::succeeded(1500, Some("Payment processed".to_string()))).unwrap();
186//! ```
187//!
188//! ## ๐ **Production Architecture**
189//!
190//! ### Multi-Server Setup
191//! ```rust,ignore
192//! #[cfg(feature = "postgres")]
193//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
194//! use qml_rs::storage::{PostgresConfig, StorageInstance};
195//! use std::sync::Arc;
196//! use qml_rs::{BackgroundJobServer, DashboardServer, ServerConfig, WorkerRegistry};
197//! let storage_config = PostgresConfig::new()
198//! .with_database_url(std::env::var("DATABASE_URL")?)
199//! .with_auto_migrate(true)
200//! .with_max_connections(50);
201//!
202//! let storage = Arc::new(StorageInstance::postgres(storage_config).await?);
203//!
204//! let registry = Arc::new(WorkerRegistry::new());
205//! let server_config = ServerConfig::new("production-server")
206//! .worker_count(20)
207//! .queues(vec!["critical".to_string(), "normal".to_string()]);
208//!
209//! let job_server = BackgroundJobServer::new(server_config, storage.clone(), registry);
210//! let dashboard = DashboardServer::new(storage.clone(), Default::default());
211//!
212//! // Note: The error types returned by job_server.start() and dashboard.start() may not match,
213//! // so this try_join! block is for illustration only and may require custom error handling in real code.
214//! tokio::try_join!(
215//! job_server.start(),
216//! dashboard.start()
217//! );
218//! Ok(())
219//! }
220//! ```
221//!
222//! ## ๐ **Configuration Examples**
223//!
224//! ### Server Configuration
225//! ```rust
226//! use qml_rs::ServerConfig;
227//! use chrono::Duration;
228//!
229//! let config = ServerConfig::new("production-server")
230//! .worker_count(20) // 20 worker threads
231//! .polling_interval(Duration::seconds(1)) // Check for jobs every second
232//! .job_timeout(Duration::minutes(5)) // 5-minute job timeout
233//! .queues(vec!["critical".to_string(), "normal".to_string()]) // Process specific queues
234//! .fetch_batch_size(10) // Fetch 10 jobs at once
235//! .enable_scheduler(true); // Enable scheduled jobs
236//! ```
237//!
238//! ### PostgreSQL Configuration
239//! ```rust,ignore
240//! #[cfg(feature = "postgres")]
241//! use qml_rs::storage::PostgresConfig;
242//! use std::time::Duration;
243//!
244//! let config = PostgresConfig::new()
245//! .with_database_url("postgresql://user:pass@host:5432/db")
246//! .with_max_connections(50) // Connection pool size
247//! .with_min_connections(5) // Minimum connections
248//! .with_connect_timeout(Duration::from_secs(10))
249//! .with_auto_migrate(true); // Run migrations automatically
250//! ```
251//!
252//! ## ๐งช **Testing Support**
253//!
254//! ### Unit Testing with Memory Storage
255//! ```rust
256//! use qml_rs::{MemoryStorage, Job, Storage};
257//!
258//! #[tokio::test]
259//! async fn test_job_processing() {
260//! let storage = MemoryStorage::new();
261//! let job = Job::new("test_job", vec!["arg1".to_string()]);
262//!
263//! storage.enqueue(&job).await.unwrap();
264//! let retrieved = storage.get(&job.id).await.unwrap().unwrap();
265//!
266//! assert_eq!(job.id, retrieved.id);
267//! }
268//! ```
269//!
270//! ### Stress Testing
271//! ```rust
272//! use qml_rs::{MemoryStorage, Job, Storage};
273//! use futures::future::join_all;
274//!
275//! #[tokio::test]
276//! async fn test_high_concurrency() {
277//! let storage = std::sync::Arc::new(MemoryStorage::new());
278//!
279//! // Create 100 jobs concurrently
280//! let jobs: Vec<_> = (0..100).map(|i| {
281//! Job::new("concurrent_job", vec![i.to_string()])
282//! }).collect();
283//!
284//! let futures: Vec<_> = jobs.iter().map(|job| {
285//! let storage = storage.clone();
286//! let job = job.clone();
287//! async move { storage.enqueue(&job).await }
288//! }).collect();
289//!
290//! let results = join_all(futures).await;
291//! assert!(results.iter().all(|r| r.is_ok()));
292//! }
293//! ```
294//!
295//! ## ๐ง **Error Handling**
296//!
297//! Comprehensive error types for robust error handling:
298//!
299//! ```rust
300//! use qml_rs::{QmlError, Result};
301//!
302//! fn handle_job_error(result: Result<()>) {
303//! match result {
304//! Ok(()) => println!("Job completed successfully"),
305//! Err(QmlError::JobNotFound { job_id }) => {
306//! println!("Job {} not found", job_id);
307//! },
308//! Err(QmlError::StorageError { message }) => {
309//! println!("Storage error: {}", message);
310//! },
311//! Err(QmlError::WorkerError { message }) => {
312//! println!("Worker error: {}", message);
313//! },
314//! Err(e) => println!("Other error: {}", e),
315//! }
316//! }
317//! ```
318//!
319//! ## ๐ **Examples**
320//!
321//! Run the included examples to see qml in action:
322//!
323//! ```bash
324//! # Basic job creation and serialization
325//! cargo run --example basic_job
326//!
327//! # Multi-backend storage operations
328//! cargo run --example storage_demo
329//!
330//! # Real-time dashboard with WebSocket
331//! cargo run --example dashboard_demo
332//!
333//! # Complete job processing with workers
334//! cargo run --example processing_demo
335//!
336//! # PostgreSQL setup and operations
337//! cargo run --example postgres_simple
338//! ```
339//!
340//! ## ๐ **Getting Started**
341//!
342//! 1. **Add qml to your project**:
343//! ```toml
344//! [dependencies]
345//! qml = "0.1.0"
346//! # For PostgreSQL support:
347//! qml = { version = "0.1.0", features = ["postgres"] }
348//! ```
349//!
350//! 2. **Define your workers** (implement the [`Worker`] trait)
351//! 3. **Choose a storage backend** ([`MemoryStorage`], [`RedisStorage`], [`PostgresStorage`])
352//! 4. **Configure and start** the [`BackgroundJobServer`]
353//! 5. **Monitor with** the [`DashboardServer`] (optional)
354//!
355//! See the [examples] for complete working implementations.
356//!
357//! [examples]:
358
359pub mod core;
360pub mod dashboard;
361pub mod error;
362pub mod processing;
363pub mod storage;
364
365// Re-export main types for convenience
366pub use core::{Job, JobState};
367pub use dashboard::{
368 DashboardConfig, DashboardServer, DashboardService, JobStatistics, QueueStatistics,
369};
370pub use error::{QmlError, Result};
371pub use processing::{
372 BackgroundJobServer, JobActivator, JobProcessor, JobScheduler, RetryPolicy, RetryStrategy,
373 ServerConfig, Worker, WorkerConfig, WorkerContext, WorkerRegistry, WorkerResult,
374};
375pub use storage::{MemoryStorage, Storage, StorageConfig, StorageError, StorageInstance};
376
377#[cfg(feature = "redis")]
378pub use storage::{RedisConfig, RedisStorage};
379
380#[cfg(feature = "postgres")]
381pub use storage::{PostgresConfig, PostgresStorage};