Skip to content

Commit 1f97f43

Browse files
authored
Merge pull request #120 from Tuntii/docs/background-jobs-recipe-6740767369578915576
docs: Add Background Jobs recipe and update Learning Path
2 parents 44245be + d1a4190 commit 1f97f43

File tree

6 files changed

+223
-3
lines changed

6 files changed

+223
-3
lines changed

docs/.agent/docs_coverage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
| Security | `recipes/csrf_protection.md` | `rustapi-extras/src/security` | OK |
1616
| Observability | `crates/rustapi_extras.md` | `rustapi-extras/src/telemetry` | OK |
1717
| **Jobs** | | | |
18-
| Job Queue | `crates/rustapi_jobs.md` | `rustapi-jobs` | OK |
18+
| Job Queue (Crate) | `crates/rustapi_jobs.md` | `rustapi-jobs` | OK |
19+
| Background Jobs (Recipe) | `recipes/background_jobs.md` | `rustapi-jobs` | NEW |
1920
| **Learning** | | | |
2021
| Structured Path | `learning/curriculum.md` | N/A | OK |
2122
| **Recipes** | | | |

docs/.agent/last_run.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"last_processed_ref": "v0.1.335",
3-
"date": "2025-02-21",
4-
"notes": "Enhanced learning path with quizzes and capstones. Fixed file upload recipe. Added deployment docs tracking."
3+
"date": "2025-02-24",
4+
"notes": "Added background jobs recipe and expanded learning path with Module 10."
55
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Docs Maintenance Run Report: 2025-02-24
2+
3+
## 1. Version Detection
4+
- **Repo Version**: `v0.1.335`
5+
- **Previous Processed Version**: `v0.1.335`
6+
- **Result**: No version change detected. Proceeding with Continuous Improvement phase.
7+
8+
## 2. Changes Summary
9+
This run focuses on expanding the cookbook and refining the learning path to include background job processing and testing.
10+
11+
### New Content
12+
- **Cookbook Recipe**: `docs/cookbook/src/recipes/background_jobs.md` - Comprehensive guide to `rustapi-jobs`.
13+
- **Learning Path Module**: Added "Module 10: Background Jobs & Testing" to `docs/cookbook/src/learning/curriculum.md`.
14+
15+
### Updates
16+
- Updated `docs/cookbook/src/SUMMARY.md` to include the new recipe.
17+
- Updated `docs/cookbook/src/learning/curriculum.md` to enhance the Phase 3 Capstone project.
18+
19+
## 3. Improvement Details
20+
- **Background Jobs**: Added a detailed recipe covering:
21+
- Defining `Job` structs and handlers.
22+
- Setting up `JobQueue` with `InMemoryBackend`.
23+
- Enqueueing jobs from API handlers.
24+
- Running the job worker.
25+
- Verification of job execution.
26+
27+
- **Learning Path**:
28+
- Added explicit module for `rustapi-jobs` usage.
29+
- Reinforced testing practices in the curriculum.
30+
31+
## 4. Open Questions / TODOs
32+
- Investigate adding `rustapi-jobs` as a re-export in `rustapi-rs` for better "batteries-included" experience in future versions.
33+
- Consider adding more backend examples (Redis, Postgres) to the cookbook recipe when environment setup allows.

docs/cookbook/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- [CSRF Protection](recipes/csrf_protection.md)
3535
- [Database Integration](recipes/db_integration.md)
3636
- [File Uploads](recipes/file_uploads.md)
37+
- [Background Jobs](recipes/background_jobs.md)
3738
- [Custom Middleware](recipes/custom_middleware.md)
3839
- [Real-time Chat](recipes/websockets.md)
3940
- [Production Tuning](recipes/high_performance.md)

docs/cookbook/src/learning/curriculum.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,26 @@ This curriculum is designed to take you from a RustAPI beginner to an advanced u
145145
2. What command generates a production Dockerfile?
146146
3. How do you enable compression for responses?
147147

148+
### Module 10: Background Jobs & Testing
149+
- **Prerequisites:** Phase 3.
150+
- **Reading:** [Background Jobs Recipe](../recipes/background_jobs.md), [Testing Strategy](../concepts/testing.md).
151+
- **Task:**
152+
1. Implement a job that sends a "Welcome" email (simulated) when a user registers.
153+
2. Write an integration test using `TestClient` to verify the registration endpoint.
154+
- **Expected Output:** Registration returns 200 immediately; console logs show "Sending welcome email to ..." shortly after. Tests pass.
155+
- **Pitfalls:** Forgetting to start the job worker loop.
156+
157+
#### 🧠 Knowledge Check
158+
1. Why use background jobs for email sending?
159+
2. Which backend is suitable for local development?
160+
3. How do you enqueue a job from a handler?
161+
148162
### 🏆 Phase 3 Capstone: "The Real-Time Collaboration Tool"
149163
**Objective:** Build a real-time collaborative note-taking app.
150164
**Requirements:**
151165
- **Auth:** Users must log in to edit notes.
152166
- **Real-time:** Changes to a note are broadcast to all viewers via WebSockets.
167+
- **Jobs:** When a note is deleted, schedule a background job to archive it (simulate archive).
153168
- **Resilience:** Rate limit API requests to prevent abuse.
154169
- **Deployment:** specify a `Dockerfile` for the application.
155170

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Background Jobs
2+
3+
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.
4+
5+
## Setup
6+
7+
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.
8+
9+
```toml
10+
[dependencies]
11+
rustapi-rs = "0.1"
12+
rustapi-jobs = "0.1"
13+
serde = { version = "1.0", features = ["derive"] }
14+
async-trait = "0.1"
15+
tokio = { version = "1.0", features = ["full"] }
16+
```
17+
18+
## Defining a Job
19+
20+
A job consists of a data structure (the payload) and an implementation of the `Job` trait.
21+
22+
```rust,no_run
23+
use rustapi_jobs::{Job, JobContext, Result};
24+
use serde::{Deserialize, Serialize};
25+
use async_trait::async_trait;
26+
27+
28+
// 1. Define the job payload
29+
#[derive(Debug, Serialize, Deserialize, Clone)]
30+
pub struct WelcomeEmailData {
31+
pub user_id: String,
32+
pub email: String,
33+
}
34+
35+
// 2. Define the job handler struct
36+
#[derive(Clone)]
37+
pub struct WelcomeEmailJob;
38+
39+
// 3. Implement the Job trait
40+
#[async_trait]
41+
impl Job for WelcomeEmailJob {
42+
// Unique name for the job type
43+
const NAME: &'static str = "send_welcome_email";
44+
45+
// The payload type
46+
type Data = WelcomeEmailData;
47+
48+
async fn execute(&self, ctx: JobContext, data: Self::Data) -> Result<()> {
49+
println!("Processing job {} (attempt {})", ctx.job_id, ctx.attempt);
50+
println!("Sending welcome email to {} ({})", data.email, data.user_id);
51+
52+
// Simulate work
53+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
54+
55+
Ok(())
56+
}
57+
}
58+
```
59+
60+
## Registering and Running the Queue
61+
62+
In your main application setup, you need to:
63+
1. Initialize the backend (Memory, Redis, or Postgres).
64+
2. Create the `JobQueue`.
65+
3. Register your job handlers.
66+
4. Start the worker loop in a background task.
67+
5. Add the `JobQueue` to your application state so handlers can use it.
68+
69+
```rust,no_run
70+
use rustapi_rs::prelude::*;
71+
use rustapi_jobs::{JobQueue, InMemoryBackend};
72+
// use crate::jobs::{WelcomeEmailJob, WelcomeEmailData}; // Import your job
73+
74+
#[tokio::main]
75+
async fn main() -> std::io::Result<()> {
76+
// 1. Initialize backend
77+
// For production, use Redis or Postgres backend
78+
let backend = InMemoryBackend::new();
79+
80+
// 2. Create queue
81+
let queue = JobQueue::new(backend);
82+
83+
// 3. Register jobs
84+
// You must register an instance of the job handler
85+
queue.register_job(WelcomeEmailJob).await;
86+
87+
// 4. Start worker in background
88+
let queue_for_worker = queue.clone();
89+
tokio::spawn(async move {
90+
if let Err(e) = queue_for_worker.start_worker().await {
91+
eprintln!("Worker failed: {}", e);
92+
}
93+
});
94+
95+
// 5. Build application
96+
RustApi::auto()
97+
.with_state(queue) // Inject queue into state
98+
.serve("127.0.0.1:3000")
99+
.await
100+
}
101+
```
102+
103+
## Enqueueing Jobs
104+
105+
You can now inject the `JobQueue` into your request handlers using the `State` extractor and enqueue jobs.
106+
107+
```rust,no_run
108+
use rustapi_rs::prelude::*;
109+
use rustapi_jobs::JobQueue;
110+
111+
#[rustapi::post("/register")]
112+
async fn register_user(
113+
State(queue): State<JobQueue>,
114+
Json(payload): Json<RegisterRequest>,
115+
) -> Result<impl IntoResponse, ApiError> {
116+
// ... logic to create user in DB ...
117+
let user_id = "user_123".to_string(); // Simulated ID
118+
119+
// Enqueue the background job
120+
// The queue will handle serialization and persistence
121+
queue.enqueue::<WelcomeEmailJob>(WelcomeEmailData {
122+
user_id,
123+
email: payload.email,
124+
}).await.map_err(|e| ApiError::InternalServerError(e.to_string()))?;
125+
126+
Ok(Json(json!({
127+
"status": "registered",
128+
"message": "Welcome email will be sent shortly"
129+
})))
130+
}
131+
132+
#[derive(Deserialize)]
133+
struct RegisterRequest {
134+
username: String,
135+
email: String,
136+
}
137+
```
138+
139+
## Resilience and Retries
140+
141+
`rustapi-jobs` handles failures automatically. If your `execute` method returns an `Err`, the job will be:
142+
1. Marked as failed.
143+
2. Optionally scheduled for retry with **exponential backoff** if retries are enabled.
144+
3. Retried up to `max_attempts` when you configure it via `EnqueueOptions`.
145+
146+
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.
147+
To customize retry behavior, use `enqueue_opts`:
148+
149+
```rust,no_run
150+
use rustapi_jobs::EnqueueOptions;
151+
152+
queue.enqueue_opts::<WelcomeEmailJob>(
153+
data,
154+
EnqueueOptions::new()
155+
.max_attempts(5) // Retry up to 5 times
156+
.delay(std::time::Duration::from_secs(60)) // Initial delay
157+
).await?;
158+
```
159+
160+
## Backends
161+
162+
While `InMemoryBackend` is great for testing and simple apps, production systems should use persistent backends:
163+
164+
- **Redis**: High performance, good for volatile queues. Enable `redis` feature in `rustapi-jobs`.
165+
- **Postgres**: Best for reliability and transactional safety. Enable `postgres` feature.
166+
167+
```toml
168+
# In Cargo.toml
169+
rustapi-jobs = { version = "0.1", features = ["redis"] }
170+
```

0 commit comments

Comments
 (0)