diff --git a/crates/rustapi-extras/src/lib.rs b/crates/rustapi-extras/src/lib.rs index 2b2d2c8..d71992a 100644 --- a/crates/rustapi-extras/src/lib.rs +++ b/crates/rustapi-extras/src/lib.rs @@ -36,7 +36,7 @@ //! //! ```toml //! [dependencies] -//! rustapi-extras = { version = "0.1", features = ["jwt", "cors", "csrf"] } +//! rustapi-extras = { version = "0.1.275", features = ["jwt", "cors", "csrf"] } //! ``` #![warn(missing_docs)] diff --git a/crates/rustapi-rs/src/lib.rs b/crates/rustapi-rs/src/lib.rs index d117d4d..dc51a6a 100644 --- a/crates/rustapi-rs/src/lib.rs +++ b/crates/rustapi-rs/src/lib.rs @@ -54,7 +54,7 @@ //! //! ```toml //! [dependencies] -//! rustapi-rs = { version = "0.1", features = ["jwt", "cors"] } +//! rustapi-rs = { version = "0.1.275", features = ["jwt", "cors"] } //! ``` // Re-export core functionality diff --git a/crates/rustapi-toon/src/lib.rs b/crates/rustapi-toon/src/lib.rs index 78b4aa0..c8a1b41 100644 --- a/crates/rustapi-toon/src/lib.rs +++ b/crates/rustapi-toon/src/lib.rs @@ -30,7 +30,7 @@ //! //! ```toml //! [dependencies] -//! rustapi-rs = { version = "0.1", features = ["toon"] } +//! rustapi-rs = { version = "0.1.275", features = ["toon"] } //! ``` //! //! ### Toon Extractor diff --git a/docs/cookbook/src/SUMMARY.md b/docs/cookbook/src/SUMMARY.md index bf3b5d2..5ae3673 100644 --- a/docs/cookbook/src/SUMMARY.md +++ b/docs/cookbook/src/SUMMARY.md @@ -35,6 +35,7 @@ - [Custom Middleware](recipes/custom_middleware.md) - [Real-time Chat](recipes/websockets.md) - [Production Tuning](recipes/high_performance.md) + - [Resilience Patterns](recipes/resilience.md) - [Deployment](recipes/deployment.md) - [HTTP/3 (QUIC)](recipes/http3_quic.md) - [Automatic Status Page](recipes/status_page.md) diff --git a/docs/cookbook/src/learning/README.md b/docs/cookbook/src/learning/README.md index 880d721..1d0280a 100644 --- a/docs/cookbook/src/learning/README.md +++ b/docs/cookbook/src/learning/README.md @@ -117,7 +117,7 @@ Build robust, observable, and secure systems. | Step | Feature | Description | |------|---------|-------------| | 1 | **Observability** | Set up [OpenTelemetry and Structured Logging](../crates/rustapi_extras.md#observability) | -| 2 | **Resilience** | Implement [Circuit Breakers and Retries](../crates/rustapi_extras.md#resilience) | +| 2 | **Resilience** | Implement [Circuit Breakers and Retries](../recipes/resilience.md) | | 3 | **Advanced Security** | Add [OAuth2 and Security Headers](../crates/rustapi_extras.md#advanced-security) | | 4 | **Optimization** | Configure [Caching and Deduplication](../crates/rustapi_extras.md#optimization) | | 5 | **Background Jobs** | Implement [Reliable Job Queues](../crates/rustapi_jobs.md) | @@ -125,6 +125,7 @@ Build robust, observable, and secure systems. **Related Cookbook Recipes:** - [rustapi-extras: The Toolbox](../crates/rustapi_extras.md) - [rustapi-jobs: The Workhorse](../crates/rustapi_jobs.md) +- [Resilience Patterns](../recipes/resilience.md) --- @@ -242,6 +243,7 @@ Each example includes: | [Custom Middleware](../recipes/custom_middleware.md) | `middleware-chain` | | [Real-time Chat](../recipes/websockets.md) | `websocket` | | [Production Tuning](../recipes/high_performance.md) | `microservices-advanced` | +| [Resilience Patterns](../recipes/resilience.md) | `microservices` | | [Deployment](../recipes/deployment.md) | `serverless-lambda` | --- diff --git a/docs/cookbook/src/recipes/resilience.md b/docs/cookbook/src/recipes/resilience.md new file mode 100644 index 0000000..5736185 --- /dev/null +++ b/docs/cookbook/src/recipes/resilience.md @@ -0,0 +1,123 @@ +# Resilience Patterns + +Building robust applications requires handling failures gracefully. RustAPI provides a suite of middleware to help your service survive partial outages, latency spikes, and transient errors. + +These patterns are essential for the "Enterprise Platform" learning path and microservices architectures. + +## Prerequisites + +Add the resilience features to your `Cargo.toml`. For example: + +```toml +[dependencies] +rustapi-rs = { version = "0.1.275", features = ["full"] } +# OR cherry-pick features +# rustapi-extras = { version = "0.1.275", features = ["circuit-breaker", "retry", "timeout"] } +``` + +## Circuit Breaker + +The Circuit Breaker pattern prevents your application from repeatedly trying to execute an operation that's likely to fail. It gives the failing service time to recover. + +### How it works +1. **Closed**: Requests flow normally. +2. **Open**: After `failure_threshold` is reached, requests fail immediately with `503 Service Unavailable`. +3. **Half-Open**: After `timeout` passes, a limited number of test requests are allowed. If they succeed, the circuit closes. + +### Usage + +```rust +use rustapi_rs::prelude::*; +use rustapi_extras::circuit_breaker::CircuitBreakerLayer; +use std::time::Duration; + +fn main() { + let app = RustApi::new() + .layer( + CircuitBreakerLayer::new() + .failure_threshold(5) // Open after 5 failures + .timeout(Duration::from_secs(30)) // Wait 30s before retrying + .success_threshold(2) // Require 2 successes to close + ) + .route("/", get(handler)); + + // ... run app +} +``` + +## Retry with Backoff + +Transient failures (network blips, temporary timeouts) can often be resolved by simply retrying the request. The `RetryLayer` handles this automatically with configurable backoff strategies. + +### Strategies +- **Exponential**: `base * 2^attempt` (Recommended for most cases) +- **Linear**: `base * attempt` +- **Fixed**: Constant delay + +### Usage + +```rust +use rustapi_rs::prelude::*; +use rustapi_extras::retry::{RetryLayer, RetryStrategy}; +use std::time::Duration; + +fn main() { + let app = RustApi::new() + .layer( + RetryLayer::new() + .max_attempts(3) + .initial_backoff(Duration::from_millis(100)) + .max_backoff(Duration::from_secs(5)) + .strategy(RetryStrategy::Exponential) + .retryable_statuses(vec![500, 502, 503, 504, 429]) + ) + .route("/", get(handler)); + + // ... run app +} +``` + +> **Warning**: Be careful when combining Retries with non-idempotent operations (like `POST` requests that charge a credit card). The middleware safely handles cloning requests, but your business logic must support it. + +## Timeouts + +Never let a request hang indefinitely. The `TimeoutLayer` enforces a hard limit on request duration, returning `408 Request Timeout` if exceeded. + +### Usage + +```rust +use rustapi_rs::prelude::*; +use rustapi_extras::timeout::TimeoutLayer; +use std::time::Duration; + +fn main() { + let app = RustApi::new() + // Fail if handler takes longer than 5 seconds + .layer(TimeoutLayer::from_secs(5)) + .route("/", get(slow_handler)); + + // ... run app +} +``` + +## Combining Layers (The Resilience Stack) + +Order matters! Timeout should be the "outermost" constraint, followed by Circuit Breaker, then Retry. + +In RustAPI (Tower) middleware, layers wrap around each other. The order you call `.layer()` wraps the *previous* service. + +**Recommended Order:** +1. **Retry** (Inner): Retries specific failures from the handler. +2. **Circuit Breaker** (Middle): Stops retrying if the system is overloaded. +3. **Timeout** (Outer): Enforces global time limit including all retries. + +```rust +let app = RustApi::new() + // 1. Retry (handles transient errors) + .layer(RetryLayer::new()) + // 2. Circuit Breaker (protects upstream) + .layer(CircuitBreakerLayer::new()) + // 3. Timeout (applies to the whole operation) + .layer(TimeoutLayer::from_secs(10)) + .route("/", get(handler)); +```