mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-21 22:56:46 +01:00
Add dependency injection example (#2466)
This commit is contained in:
parent
c34041510c
commit
3b43b257e7
2 changed files with 182 additions and 0 deletions
13
examples/dependency-injection/Cargo.toml
Normal file
13
examples/dependency-injection/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "example-dependency-injection"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
axum = { path = "../../axum", features = ["tracing", "macros"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uuid = { version = "1.0", features = ["serde", "v4"] }
|
169
examples/dependency-injection/src/main.rs
Normal file
169
examples/dependency-injection/src/main.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
//! 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(|_| "example_dependency_injection=debug".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.unwrap();
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue