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.
Add the resilience features to your Cargo.toml. For example:
[dependencies]
rustapi-rs = { version = "0.1.335", features = ["full"] }
# OR cherry-pick features
# rustapi-extras = { version = "0.1.335", features = ["circuit-breaker", "retry", "timeout"] }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.
- Closed: Requests flow normally.
- Open: After
failure_thresholdis reached, requests fail immediately with503 Service Unavailable. - Half-Open: After
timeoutpasses, a limited number of test requests are allowed. If they succeed, the circuit closes.
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
}Transient failures (network blips, temporary timeouts) can often be resolved by simply retrying the request. The RetryLayer handles this automatically with configurable backoff strategies.
- Exponential:
base * 2^attempt(Recommended for most cases) - Linear:
base * attempt - Fixed: Constant delay
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
POSTrequests that charge a credit card). The middleware safely handles cloning requests, but your business logic must support it.
Never let a request hang indefinitely. The TimeoutLayer enforces a hard limit on request duration, returning 408 Request Timeout if exceeded.
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
}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:
- Retry (Inner): Retries specific failures from the handler.
- Circuit Breaker (Middle): Stops retrying if the system is overloaded.
- Timeout (Outer): Enforces global time limit including all retries.
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));