mirror of
https://github.com/tokio-rs/axum.git
synced 2025-09-27 13:00:39 +00:00
170 lines
4.1 KiB
Rust
170 lines
4.1 KiB
Rust
//! Run with
|
|
//!
|
|
//! ```not_rust
|
|
//! cargo run -p example-dependency-injection
|
|
//! ```
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
routing::{get, post},
|
|
Json, Router,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::net::TcpListener;
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.init();
|
|
|
|
let user_repo = InMemoryUserRepo::default();
|
|
|
|
// We generally have two ways to inject dependencies:
|
|
//
|
|
// 1. Using trait objects (`dyn SomeTrait`)
|
|
// - Pros
|
|
// - Likely leads to simpler code due to fewer type parameters.
|
|
// - Cons
|
|
// - Less flexible because we can only use object safe traits
|
|
// - Small amount of additional runtime overhead due to dynamic dispatch.
|
|
// This is likely to be negligible.
|
|
// 2. Using generics (`T where T: SomeTrait`)
|
|
// - Pros
|
|
// - More flexible since all traits can be used.
|
|
// - No runtime overhead.
|
|
// - Cons:
|
|
// - Additional type parameters and trait bounds can lead to more complex code and
|
|
// boilerplate.
|
|
//
|
|
// Using trait objects is recommended unless you really need generics.
|
|
|
|
let using_dyn = Router::new()
|
|
.route("/users/{id}", get(get_user_dyn))
|
|
.route("/users", post(create_user_dyn))
|
|
.with_state(AppStateDyn {
|
|
user_repo: Arc::new(user_repo.clone()),
|
|
});
|
|
|
|
let using_generic = Router::new()
|
|
.route("/users/{id}", get(get_user_generic::<InMemoryUserRepo>))
|
|
.route("/users", post(create_user_generic::<InMemoryUserRepo>))
|
|
.with_state(AppStateGeneric { user_repo });
|
|
|
|
let app = Router::new()
|
|
.nest("/dyn", using_dyn)
|
|
.nest("/generic", using_generic);
|
|
|
|
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
|
|
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
|
axum::serve(listener, app).await;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct AppStateDyn {
|
|
user_repo: Arc<dyn UserRepo>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct AppStateGeneric<T> {
|
|
user_repo: T,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Clone)]
|
|
struct User {
|
|
id: Uuid,
|
|
name: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct UserParams {
|
|
name: String,
|
|
}
|
|
|
|
async fn create_user_dyn(
|
|
State(state): State<AppStateDyn>,
|
|
Json(params): Json<UserParams>,
|
|
) -> Json<User> {
|
|
let user = User {
|
|
id: Uuid::new_v4(),
|
|
name: params.name,
|
|
};
|
|
|
|
state.user_repo.save_user(&user);
|
|
|
|
Json(user)
|
|
}
|
|
|
|
async fn get_user_dyn(
|
|
State(state): State<AppStateDyn>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<User>, StatusCode> {
|
|
match state.user_repo.get_user(id) {
|
|
Some(user) => Ok(Json(user)),
|
|
None => Err(StatusCode::NOT_FOUND),
|
|
}
|
|
}
|
|
|
|
async fn create_user_generic<T>(
|
|
State(state): State<AppStateGeneric<T>>,
|
|
Json(params): Json<UserParams>,
|
|
) -> Json<User>
|
|
where
|
|
T: UserRepo,
|
|
{
|
|
let user = User {
|
|
id: Uuid::new_v4(),
|
|
name: params.name,
|
|
};
|
|
|
|
state.user_repo.save_user(&user);
|
|
|
|
Json(user)
|
|
}
|
|
|
|
async fn get_user_generic<T>(
|
|
State(state): State<AppStateGeneric<T>>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<User>, StatusCode>
|
|
where
|
|
T: UserRepo,
|
|
{
|
|
match state.user_repo.get_user(id) {
|
|
Some(user) => Ok(Json(user)),
|
|
None => Err(StatusCode::NOT_FOUND),
|
|
}
|
|
}
|
|
|
|
trait UserRepo: Send + Sync {
|
|
fn get_user(&self, id: Uuid) -> Option<User>;
|
|
|
|
fn save_user(&self, user: &User);
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
struct InMemoryUserRepo {
|
|
map: Arc<Mutex<HashMap<Uuid, User>>>,
|
|
}
|
|
|
|
impl UserRepo for InMemoryUserRepo {
|
|
fn get_user(&self, id: Uuid) -> Option<User> {
|
|
self.map.lock().unwrap().get(&id).cloned()
|
|
}
|
|
|
|
fn save_user(&self, user: &User) {
|
|
self.map.lock().unwrap().insert(user.id, user.clone());
|
|
}
|
|
}
|