-
-
Notifications
You must be signed in to change notification settings - Fork 2
docs: Add Background Jobs recipe and update Learning Path #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| { | ||
| "last_processed_ref": "v0.1.335", | ||
| "date": "2025-02-21", | ||
| "notes": "Enhanced learning path with quizzes and capstones. Fixed file upload recipe. Added deployment docs tracking." | ||
| "date": "2025-02-24", | ||
| "notes": "Added background jobs recipe and expanded learning path with Module 10." | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Docs Maintenance Run Report: 2025-02-24 | ||
|
|
||
| ## 1. Version Detection | ||
| - **Repo Version**: `v0.1.335` | ||
| - **Previous Processed Version**: `v0.1.335` | ||
| - **Result**: No version change detected. Proceeding with Continuous Improvement phase. | ||
|
|
||
| ## 2. Changes Summary | ||
| This run focuses on expanding the cookbook and refining the learning path to include background job processing and testing. | ||
|
|
||
| ### New Content | ||
| - **Cookbook Recipe**: `docs/cookbook/src/recipes/background_jobs.md` - Comprehensive guide to `rustapi-jobs`. | ||
| - **Learning Path Module**: Added "Module 10: Background Jobs & Testing" to `docs/cookbook/src/learning/curriculum.md`. | ||
|
|
||
| ### Updates | ||
| - Updated `docs/cookbook/src/SUMMARY.md` to include the new recipe. | ||
| - Updated `docs/cookbook/src/learning/curriculum.md` to enhance the Phase 3 Capstone project. | ||
|
|
||
| ## 3. Improvement Details | ||
| - **Background Jobs**: Added a detailed recipe covering: | ||
| - Defining `Job` structs and handlers. | ||
| - Setting up `JobQueue` with `InMemoryBackend`. | ||
| - Enqueueing jobs from API handlers. | ||
| - Running the job worker. | ||
| - Verification of job execution. | ||
|
|
||
| - **Learning Path**: | ||
| - Added explicit module for `rustapi-jobs` usage. | ||
| - Reinforced testing practices in the curriculum. | ||
|
|
||
| ## 4. Open Questions / TODOs | ||
| - Investigate adding `rustapi-jobs` as a re-export in `rustapi-rs` for better "batteries-included" experience in future versions. | ||
| - Consider adding more backend examples (Redis, Postgres) to the cookbook recipe when environment setup allows. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # Background Jobs | ||
|
|
||
| RustAPI provides a robust background job processing system through the `rustapi-jobs` crate. This allows you to offload time-consuming tasks (like sending emails, processing images, or generating reports) from the main request/response cycle, keeping your API fast and responsive. | ||
|
|
||
| ## Setup | ||
|
|
||
| First, add `rustapi-jobs` to your `Cargo.toml`. Since `rustapi-jobs` is not re-exported by the main crate by default, you must include it explicitly. | ||
|
|
||
| ```toml | ||
| [dependencies] | ||
| rustapi-rs = "0.1" | ||
| rustapi-jobs = "0.1" | ||
| serde = { version = "1.0", features = ["derive"] } | ||
| async-trait = "0.1" | ||
| tokio = { version = "1.0", features = ["full"] } | ||
| ``` | ||
|
|
||
| ## Defining a Job | ||
|
|
||
| A job consists of a data structure (the payload) and an implementation of the `Job` trait. | ||
|
|
||
| ```rust,no_run | ||
| use rustapi_jobs::{Job, JobContext, Result}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use async_trait::async_trait; | ||
|
|
||
|
|
||
| // 1. Define the job payload | ||
| #[derive(Debug, Serialize, Deserialize, Clone)] | ||
| pub struct WelcomeEmailData { | ||
| pub user_id: String, | ||
| pub email: String, | ||
| } | ||
|
|
||
| // 2. Define the job handler struct | ||
| #[derive(Clone)] | ||
| pub struct WelcomeEmailJob; | ||
|
|
||
| // 3. Implement the Job trait | ||
| #[async_trait] | ||
| impl Job for WelcomeEmailJob { | ||
| // Unique name for the job type | ||
| const NAME: &'static str = "send_welcome_email"; | ||
|
|
||
| // The payload type | ||
| type Data = WelcomeEmailData; | ||
|
|
||
| async fn execute(&self, ctx: JobContext, data: Self::Data) -> Result<()> { | ||
| println!("Processing job {} (attempt {})", ctx.job_id, ctx.attempt); | ||
| println!("Sending welcome email to {} ({})", data.email, data.user_id); | ||
|
|
||
| // Simulate work | ||
| tokio::time::sleep(std::time::Duration::from_millis(100)).await; | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Registering and Running the Queue | ||
|
|
||
| In your main application setup, you need to: | ||
| 1. Initialize the backend (Memory, Redis, or Postgres). | ||
| 2. Create the `JobQueue`. | ||
| 3. Register your job handlers. | ||
| 4. Start the worker loop in a background task. | ||
| 5. Add the `JobQueue` to your application state so handlers can use it. | ||
|
|
||
| ```rust,no_run | ||
| use rustapi_rs::prelude::*; | ||
| use rustapi_jobs::{JobQueue, InMemoryBackend}; | ||
| // use crate::jobs::{WelcomeEmailJob, WelcomeEmailData}; // Import your job | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> std::io::Result<()> { | ||
| // 1. Initialize backend | ||
| // For production, use Redis or Postgres backend | ||
| let backend = InMemoryBackend::new(); | ||
|
|
||
| // 2. Create queue | ||
| let queue = JobQueue::new(backend); | ||
|
|
||
| // 3. Register jobs | ||
| // You must register an instance of the job handler | ||
| queue.register_job(WelcomeEmailJob).await; | ||
|
|
||
| // 4. Start worker in background | ||
| let queue_for_worker = queue.clone(); | ||
| tokio::spawn(async move { | ||
| if let Err(e) = queue_for_worker.start_worker().await { | ||
| eprintln!("Worker failed: {}", e); | ||
| } | ||
| }); | ||
|
|
||
| // 5. Build application | ||
| RustApi::auto() | ||
| .with_state(queue) // Inject queue into state | ||
| .serve("127.0.0.1:3000") | ||
| .await | ||
| } | ||
| ``` | ||
|
|
||
| ## Enqueueing Jobs | ||
|
|
||
| You can now inject the `JobQueue` into your request handlers using the `State` extractor and enqueue jobs. | ||
|
|
||
| ```rust,no_run | ||
| use rustapi_rs::prelude::*; | ||
| use rustapi_jobs::JobQueue; | ||
|
|
||
| #[rustapi::post("/register")] | ||
| async fn register_user( | ||
| State(queue): State<JobQueue>, | ||
| Json(payload): Json<RegisterRequest>, | ||
| ) -> Result<impl IntoResponse, ApiError> { | ||
| // ... logic to create user in DB ... | ||
Tuntii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let user_id = "user_123".to_string(); // Simulated ID | ||
|
|
||
| // Enqueue the background job | ||
| // The queue will handle serialization and persistence | ||
| queue.enqueue::<WelcomeEmailJob>(WelcomeEmailData { | ||
| user_id, | ||
| email: payload.email, | ||
| }).await.map_err(|e| ApiError::InternalServerError(e.to_string()))?; | ||
|
|
||
| Ok(Json(json!({ | ||
| "status": "registered", | ||
| "message": "Welcome email will be sent shortly" | ||
| }))) | ||
Tuntii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| #[derive(Deserialize)] | ||
| struct RegisterRequest { | ||
| username: String, | ||
| email: String, | ||
| } | ||
| ``` | ||
|
|
||
| ## Resilience and Retries | ||
|
|
||
| `rustapi-jobs` handles failures automatically. If your `execute` method returns an `Err`, the job will be: | ||
| 1. Marked as failed. | ||
| 2. Optionally scheduled for retry with **exponential backoff** if retries are enabled. | ||
| 3. Retried up to `max_attempts` when you configure it via `EnqueueOptions`. | ||
|
|
||
| By default, `EnqueueOptions::new()` sets `max_attempts` to `0`, so a failed job will **not** be retried unless you explicitly opt in by calling `.max_attempts(...)` with a value greater than the current `attempts` count. | ||
| To customize retry behavior, use `enqueue_opts`: | ||
|
|
||
| ```rust,no_run | ||
| use rustapi_jobs::EnqueueOptions; | ||
|
|
||
| queue.enqueue_opts::<WelcomeEmailJob>( | ||
| data, | ||
| EnqueueOptions::new() | ||
| .max_attempts(5) // Retry up to 5 times | ||
| .delay(std::time::Duration::from_secs(60)) // Initial delay | ||
| ).await?; | ||
| ``` | ||
|
|
||
| ## Backends | ||
|
|
||
| While `InMemoryBackend` is great for testing and simple apps, production systems should use persistent backends: | ||
|
|
||
| - **Redis**: High performance, good for volatile queues. Enable `redis` feature in `rustapi-jobs`. | ||
| - **Postgres**: Best for reliability and transactional safety. Enable `postgres` feature. | ||
|
|
||
| ```toml | ||
| # In Cargo.toml | ||
| rustapi-jobs = { version = "0.1", features = ["redis"] } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application wiring example uses builder methods that don't exist on
RustApiin this repo (.with_state(...)/.serve(...)), and themainreturn type (std::io::Result<()>) won’t matchRustApi::run(...)’s error type. Update the snippet to use.state(queue)and.run("127.0.0.1:3000")(or returnResult<(), Box<dyn Error + Send + Sync>>).