Strategien zur Dependency Injection in Axum und Actix Web
Ethan Miller
Product Engineer · Leapcell

Einleitung
Die Entwicklung robuster und wartbarer Webdienste in Rust, insbesondere mit Frameworks wie Axum und Actix Web, beinhaltet oft die Verwaltung komplexer Anwendungszustände und externer Abhängigkeiten. Wenn Anwendungen wachsen, kann das explizite Übergeben jeder benötigten Ressource durch mehrere Funktionsschichten umständlich werden und zu Boilerplate-Code und eng gekoppelten Komponenten führen. Hier glänzt Dependency Injection (DI). Dependency Injection ist ein leistungsfähiges Entwurfsmuster, das lose Kopplung fördert und die Testbarkeit und Wartbarkeit Ihrer Codebasis verbessert, indem es die Erstellung und Verwendung von Objekten entkoppelt. Im Kontext der Webentwicklung bedeutet dies die mühelose Verwaltung von Datenbankverbindungen, API-Clients, Konfigurationseinstellungen oder sogar anderen Diensten, von denen Ihre Handler abhängen. Dieser Artikel wird sich mit den verschiedenen Ansätzen zur Implementierung von Dependency Injection in Axum und Actix Web befassen und praktische Beispiele liefern, die veranschaulichen, wie Sie diese Muster nutzen können, um flexiblere und skalierbarere Rust-Webanwendungen zu erstellen.
Kernkonzepte für Dependency Injection
Bevor wir uns mit den Implementierungsdetails befassen, lassen Sie uns einige grundlegende Konzepte klären, die für das Verständnis von Dependency Injection von zentraler Bedeutung sind.
- Abhängigkeit (Dependency): In der Softwareentwicklung bezieht sich eine Abhängigkeit auf ein Objekt oder Modul, das ein anderes Objekt oder Modul zur korrekten Funktion benötigt. Zum Beispiel könnte ein Benutzerservice von einem Datenbankverbindungspool abhängen, um Benutzerdaten zu speichern und abzurufen.
- Inversion of Control (IoC): IoC ist ein Entwurfsprinzip, bei dem die Kontrolle über die Objekterstellung und den Lebenszyklus vom Anwendungscode auf ein Framework oder einen Container übertragen wird. Dependency Injection ist eine spezifische Form von IoC. Anstatt dass ein Objekt seine Abhängigkeiten erstellt, werden ihm die Abhängigkeiten von einer externen Entität bereitgestellt.
- Dependency Injection (DI): DI ist eine Technik, bei der ein Objekt andere Objekte erhält, von denen es abhängt, anstatt sie selbst zu erstellen. Dies fördert lose Kopplung, da das abhängige Objekt nicht wissen muss, wie seine Abhängigkeiten konstruiert werden.
- Service Locator: Ein alternatives Muster, bei dem ein Objekt explizit einen "Locator" nach seinen Abhängigkeiten fragt. Obwohl es direkte Kopplung reduzieren kann, führt es eine "globale Abhängigkeit" vom Locator selbst ein, was das Testen erschweren und Abhängigkeiten verbergen kann. DI wird im Allgemeinen dem Service Locator vorgezogen.
- Container/Injector: Eine Komponente, die für die Instanziierung, Konfiguration und Verwaltung des Lebenszyklus von Objekten sowie für die Injektion ihrer Abhängigkeiten verantwortlich ist. Obwohl explizite DI-Container in Rust aufgrund seines starken Typsystems und seines Besitzmodells weniger verbreitet sind, manifestiert sich das Konzept eines "Containers" oft als gemeinsam genutzter Anwendungszustand oder benutzerdefinierte Strukturen.
Implementierung von Dependency Injection in Axum und Actix Web
In Rust-Webframeworks drehen sich die primären Methoden für Dependency Injection oft um das Teilen von Zustand über Handler oder die Nutzung von Extrahierungsmustern. Wir werden diese Strategien mit praktischen Codebeispielen untersuchen.
Axum: Zustand und Extrahierer
Axum bietet mit seiner tower::Service
-Grundlage elegante Möglichkeiten zur Verwaltung von gemeinsam genutztem Zustand, was sich natürlich für Dependency Injection eignet.
Strategie 1: Gemeinsamer Anwendungszustand
Der gängigste Ansatz in Axum ist, alle gemeinsam genutzten Ressourcen Ihrer Anwendung in einer einzigen AppState
-Struktur zu kapseln und sie dann über eine Extension
für Handler zugänglich zu machen.
Stellen wir uns vor, wir haben einen UserService
, der mit einer Datenbank interagiert, und einen Mailer
-Dienst zum Versenden von E-Mails.
// src/main.rs use axum::{ extract::{Extension, State}, routing::{get, post}, Json, Router, }; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; use uuid::Uuid; // --- Abhängigkeiten --- // Ein Mock-Datenbankclient #[derive(Debug, Clone)] struct DbClient { // In einer realen App wäre dies ein Connection Pool wie sqlx::PgPool } impl DbClient { async fn new() -> Self { println!("Initialisiere DbClient..."); tokio::time::sleep(std::time::Duration::from_millis(50)).await; // Simuliere asynchrone Initialisierung DbClient {} } async fn fetch_user(&self, id: Uuid) -> Option<User> { println!("Rufe Benutzer {} aus der DB ab...", id); tokio::time::sleep(std::time::Duration::from_millis(100)).await; if id == Uuid::parse_str("c9f0b1a0-3e3e-4d4d-8a8a-0a0a0a0a0a0a").unwrap() { Some(User { id, name: "Alice".to_string(), email: "alice@example.com".to_string(), }) } else { None } } async fn create_user(&self, user: User) { println!("Erstelle Benutzer in der DB: {:?}", user); tokio::time::sleep(std::time::Duration::from_millis(50)).await; } } // Ein Mock-Mailer-Dienst #[derive(Debug, Clone)] struct Mailer { // In einer realen App könnte dies ein Client für SendGrid, Mailchimp usw. sein. } impl Mailer { async fn new() -> Self { println!("Initialisiere Mailer..."); Mailer {} } async fn send_welcome_email(&self, email: String, name: String) { println!("Sende Willkommens-E-Mail an {} ({})", name, email); tokio::time::sleep(std::time::Duration::from_millis(70)).await; // Logik zum Senden von E-Mails } } // --- Anwendungsdienste (Geschäftslogik) --- #[derive(Debug, Clone)] struct UserService { db_client: DbClient, } impl UserService { fn new(db_client: DbClient) -> Self { UserService { db_client } } async fn get_user_by_id(&self, id: Uuid) -> Option<User> { self.db_client.fetch_user(id).await } async fn register_user(&self, new_user: NewUser) -> User { let user = User { id: Uuid::new_v4(), name: new_user.name, email: new_user.email, }; self.db_client.create_user(user.clone()).await; user } } // --- DTOs und Modelle --- #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct User { id: Uuid, name: String, email: String, } #[derive(Debug, Clone, serde::Deserialize)] struct NewUser { name: String, email: String, } // --- Anwendungszustand --- // Diese Struktur enthält unsere gemeinsam genutzten Dienste/Abhängigkeiten #[derive(Clone)] struct AppState { user_service: UserService, mailer: Mailer, } // Eine robustere Methode unter Verwendung von Arc für komplexe Dienste, die über Aufgaben hinweg gemeinsam genutzt und möglicherweise mutiert werden; // (obwohl sie hier meistens `Clone` sind). // Wenn ein Dienst selbst internen veränderlichen Zustand hat, der über *verschiedene* Anfragen oder Handler hinweg geteilt und modifiziert werden muss, // sollte er in Arc<Mutex<T>> oder Arc<RwLock<T>> gekapselt werden. // Für unseren `UserService` und `Mailer` hier ist ihre interne `DbClient` und `Mailer` // selbst `Clone`, sodass eine einfache Klonung von `AppState` ausreicht. // Zur Demonstration, nehmen wir an, DbClient selbst ist komplex und teuer zu klonen, // und UserService hat einen Arc dazu. #[derive(Clone)] struct AppStateArc { user_service: Arc<UserService>, // Wenn UserService teuer zu klonen oder zu modifizieren wäre mailer: Arc<Mailer>, } // --- Handler --- async fn get_user_handler( // Verwendung des State-Extrahierers (erfordert, dass `AppState` clonebar ist) State(app_state): State<AppState>, axum::extract::Path(user_id): axum::extract::Path<Uuid>, ) -> Result<Json<User>, axum::http::StatusCode> { println!("Bearbeite get_user_handler für ID: {}", user_id); match app_state.user_service.get_user_by_id(user_id).await { Some(user) => Ok(Json(user)), None => Err(axum::http::StatusCode::NOT_FOUND), } } async fn register_user_handler( // Verwendung des State-Extrahierers (erfordert, dass `AppState` clonebar ist) State(app_state): State<AppState>, Json(new_user): Json<NewUser>, ) -> Json<User> { println!("Bearbeite register_user_handler für Benutzer: {}", new_user.name); let registered_user = app_state.user_service.register_user(new_user).await; let _ = app_state .mailer .send_welcome_email(registered_user.email.clone(), registered_user.name.clone()) .await; Json(registered_user) } async fn get_user_handler_arc( // Verwendung des State-Extrahierers mit Arc<AppStateArc> // Dies ist nützlich, wenn der AppState selbst komplexe Elemente enthält, die Sie nicht oft klonen möchten State(app_state): State<Arc<AppStateArc>>, axum::extract::Path(user_id): axum::extract::Path<Uuid>, ) -> Result<Json<User>, axum::http::StatusCode> { println!("Bearbeite get_user_handler_arc für ID: {}", user_id); match app_state.user_service.get_user_by_id(user_id).await { Some(user) => Ok(Json(user)), None => Err(axum::http::StatusCode::NOT_FOUND), } } #[tokio::main] async fn main() { // Initialisiere Abhängigkeiten let db_client = DbClient::new().await; let mailer = Mailer::new().await; // Initialisiere Anwendungsdienste let user_service = UserService::new(db_client.clone()); // db_client ist Clone) // Erstelle Anwendungszustand let app_state = AppState { user_service: user_service.clone(), // user_service ist Clone) mailer: mailer.clone(), // mailer ist Clone) }; // Für Arc-Beispiel: let app_state_arc = Arc::new(AppStateArc { user_service: Arc::new(user_service), mailer: Arc::new(mailer), }); // Baue Axum-Anwendung let app = Router::new() .route("/users/:id", get(get_user_handler)) .route("/users", post(register_user_handler)) // Route für das Arc-Beispiel .route("/arc/users/:id", get(get_user_handler_arc)) .with_state(app_state) // Hänge AppState an den Router an .with_state(app_state_arc); // Sie können bei Bedarf mehrere Zustände anhängen. let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("Axum Server lauscht auf {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); }
In diesem Beispiel:
- Wir definieren
DbClient
undMailer
als unsere Low-Level-Abhängigkeiten. UserService
ist ein High-Level-Dienst, der vonDbClient
abhängt.AppState
ist eine Struktur, dieUserService
undMailer
sammelt. Wichtig ist, dassAppState
(und seine enthaltenen Dienste)Clone
implementieren muss, da Axum den Zustand für jede eingehende Anfrage klont. Wenn ein Dienst selbst nicht-Clone
-Typen enthält oder thread-sichere Mutationen benötigt, sollte er inArc<Mutex<T>>
oderArc<RwLock<T>>
gekapselt werden.- In
main
instanziieren wir alle Abhängigkeiten und Dienste. - Dann hängen wir
AppState
mit.with_state(app_state)
an den Axum-Router an. - Handler greifen auf diese Abhängigkeiten über den
State<AppState>
-Extraktor zu. Axum stellt automatisch die korrekteAppState
-Instanz bereit.
Dieses Muster ist sehr idiomatisch für Axum und bietet klare Lifetime-Verwaltung und explizite Abhängigkeitsdeklarationen.
Strategie 2: Benutzerdefinierter Extrahierer für bestimmte Abhängigkeiten
Während State
großartig für den gesamten Anwendungszustand ist, möchten Sie manchmal nur eine bestimmte Abhängigkeit oder eine Kombination von Abhängigkeiten injizieren, möglicherweise mit zusätzlicher Logik. Sie können dafür benutzerdefinierte Extrahierer erstellen.
// Fortsetzung des vorherigen Axum-Beispiels, definieren Sie einen benutzerdefinierten UserService-Extrahierer use async_trait::async_trait; use axum::{ extract::{self}, request::Parts, Request, }; // ... (DbClient, Mailer, User, NewUser, UserService, AppState, AppStateArc Definitionen wie oben) ... // Unser benutzerdefinierter UserService-Extrahierer struct InjectedUserService(UserService); #[async_trait] impl<S> FromRequestParts<S> for InjectedUserService where S: Send + Sync, AppState: FromRequestParts<S>, // Sicherstellen, dass AppState extrahiert werden kann { type Rejection = <AppState as FromRequestParts<S>>::Rejection; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { let app_state = AppState::from_request_parts(parts, state).await?; Ok(InjectedUserService(app_state.user_service.clone())) } } // Ein Handler, der den benutzerdefinierten Extrahierer verwendet async fn get_user_with_custom_extractor( InjectedUserService(user_service): InjectedUserService, // Unser benutzerdefinierter Dienst wird extrahiert axum::extract::Path(user_id): axum::extract::Path<Uuid>, ) -> Result<Json<User>, axum::http::StatusCode> { println!( "Bearbeite get_user_with_custom_extractor für ID: {}", user_id ); match user_service.get_user_by_id(user_id).await { Some(user) => Ok(Json(user)), None => Err(axum::http::StatusCode::NOT_FOUND), } } // Fügen Sie diese Route zu main() hinzu // .route("/custom_extracted_users/:id", get(get_user_with_custom_extractor))
Dieser benutzerdefinierte Extrahierer holt sich AppState
und extrahiert dann den UserService
daraus. Dies kann von Vorteil sein, wenn:
- Sie die Quelle einer Abhängigkeit abstrahieren möchten (z. B. sie könnte aus dem Zustand oder einem Anforderungsheader stammen).
- Sie Validierungs- oder Autorisierungslogik durchführen möchten, bevor der Handler die Abhängigkeit erhält.
- Sie eine spezifische Ansicht oder Zusammensetzung von Diensten injizieren möchten, anstatt die gesamte
AppState
.
Actix Web: Anwendungsdaten und Extrahierer-Traits
Actix Web bietet ebenfalls robuste Mechanismen für Dependency Injection, hauptsächlich über Anwendungsdaten (web::Data
) und benutzerdefinierte Extrahierer.
Strategie 1: Gemeinsame Anwendungsdaten (web::Data
)
Ähnlich wie Axums State
verwendet Actix Web web::Data
, um anwendungsweite Zustände und Dienste über Anfragen hinweg gemeinsam zu nutzen. Die Daten werden intern in Arc
verpackt, was einen Thread-sicheren Zugriff gewährleistet.
Passen wir unser vorheriges Beispiel für Actix Web an.
// src/main.rs use actix_web::{ get, post, web, App, HttpResponse, HttpServer, Responder, }; use std::sync::Arc; use uuid::Uuid; // --- Abhängigkeiten --- // (DbClient, Mailer, User, NewUser Definitionen wie oben - kopieren Sie sie einfach) // Stellen Sie sicher, dass DbClient und Mailer Clone sind, wenn Sie sie nicht in Arc innerhalb des Service verpacken, // oder wenn der Dienst selbst Arc ist. Für Actix Webs Data muss der interne Typ Send + Sync + 'static sein. #[derive(Debug, Clone)] struct DbClient {} impl DbClient { async fn new() -> Self { println!("Initialisiere DbClient..."); tokio::time::sleep(std::time::Duration::from_millis(50)).await; DbClient {} } async fn fetch_user(&self, id: Uuid) -> Option<User> { println!("Rufe Benutzer {} aus der DB ab...", id); tokio::time::sleep(std::time::Duration::from_millis(100)).await; if id == Uuid::parse_str("c9f0b1a0-3e3e-4d4d-8a8a-0a0a0a0a0a0a").unwrap() { Some(User { id, name: "Bob".to_string(), email: "bob@example.com".to_string(), }) } else { None } } async fn create_user(&self, user: User) { println!("Erstelle Benutzer in der DB: {:?}", user); tokio::time::sleep(std::time::Duration::from_millis(50)).await; } } #[derive(Debug, Clone)] struct Mailer {} impl Mailer { prv async fn new() -> Self { println!("Initialisiere Mailer..."); Mailer {} } async fn send_welcome_email(&self, email: String, name: String) { println!("Sende Willkommens-E-Mail an {} ({})", name, email); tokio::time::sleep(std::time::Duration::from_millis(70)).await; } } // --- Anwendungsdienste (Geschäftslogik) --- #[derive(Debug)] // Clone hier für UserService entfernt, um Arc-Nutzung in AppData zu zeigen struct UserService { db_client: DbClient, } impl UserService { fn new(db_client: DbClient) -> Self { UserService { db_client } } async fn get_user_by_id(&self, id: Uuid) -> Option<User> { self.db_client.fetch_user(id).await } async fn register_user(&self, new_user: NewUser) -> User { let user = User { id: Uuid::new_v4(), name: new_user.name, email: new_user.email, }; self.db_client.create_user(user.clone()).await; user } } // --- DTOs und Modelle --- #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct User { id: Uuid, name: String, email: String, } #[derive(Debug, Clone, serde::Deserialize)] struct NewUser { name: String, email: String, } // --- Handler --- #[get("/users/{user_id}")] async fn get_user_handler_actix( user_id: web::Path<Uuid>, // Injiziere UserService über web::Data, das intern einen Arc hält user_service: web::Data<UserService>, ) -> impl Responder { println!("Bearbeite get_user_handler_actix für ID: {}", user_id); match user_service.get_user_by_id(user_id.into_inner()).await { Some(user) => HttpResponse::Ok().json(user), None => HttpResponse::NotFound().body("Benutzer nicht gefunden"), } } #[post("/users")] async fn register_user_handler_actix( new_user_data: web::Json<NewUser>, // Injiziere sowohl UserService als auch Mailer user_service: web::Data<UserService>, mailer: web::Data<Mailer>, ) -> impl Responder { println!( "Bearbeite register_user_handler_actix für Benutzer: {}", new_user_data.name ); let registered_user = user_service .register_user(new_user_data.into_inner()) .await; let _ = mailer .send_welcome_email(registered_user.email.clone(), registered_user.name.clone()) .await; HttpResponse::Created().json(registered_user) } #[actix_web::main] async fn main() -> std::io::Result<()> { // Initialisiere Abhängigkeiten let db_client = DbClient::new().await; let mailer = Mailer::new().await; // Initialisiere Anwendungsdienste // UserService selbst ist nicht Clone, also packen wir es für web::Data in Arc ein let user_service = Arc::new(UserService::new(db_client)); HttpServer::new(move || { App::new() // Hänge Abhängigkeiten als Anwendungsdaten an // web::Data verpackt den Typ intern in Arc<T> .app_data(web::Data::new(user_service.clone())) // UserService muss Send + Sync + 'static sein .app_data(web::Data::new(mailer.clone())) // Mailer muss Send + Sync + 'static sein .service(get_user_handler_actix) .service(register_user_handler_actix) }) .bind(("127.0.0.1", 8080))? // Ignoriere diese Warnung für die Ausführung im Code-Sandbox .run() .await }
Im Actix Web-Beispiel:
- Wir definieren unsere
DbClient
,Mailer
undUserService
ähnlich wie bei Axum. - In
main
instanziieren wirUserService
undMailer
. Daweb::Data
seinen Inhalt intern in einenArc
verpackt, müssenUserService
undMailer
selbst nicht unbedingtClone
sein, wenn sie einmal inArc
gekapselt sind. Sie müssen jedochSend + Sync + 'static
sein. - Wir hängen diese Dienste mit
.app_data(web::Data::new(...))
an dieApp
-Instanz an. Wir verwenden.clone()
füruser_service
, da dieHttpServer::new
-Methode einenFnMut
-Closure erwartet und diese den Wert verbraucht. Das Klonen desArc
ist günstig. - Handler empfangen diese Abhängigkeiten, indem sie sie einfach als Argumente vom Typ
web::Data<T>
deklarieren, wobeiT
der Typ der Abhängigkeit ist. Das Extrahierersystem von Actix Web löst die korrekten Daten automatisch auf und injiziert sie.
Dieser web::Data
-Ansatz ist der gebräuchlichste und robusteste Weg, um Abhängigkeiten in Actix Web zu verwalten.
Strategie 2: Implementierung eines benutzerdefinierten Extrahierers
Actix Web erlaubt auch benutzerdefinierte Extrahierer mit dem FromRequest
-Trait, was eine granulare Kontrolle über die Dependency Injection bietet, ähnlich wie benutzerdefinierte Extrahierer in Axum.
// Fortsetzung des vorherigen Actix Web-Beispiels, definieren Sie einen benutzerdefinierten UserService-Extrahierer use actix_web::FromRequest; // Trait importieren use futures::future::{ready, Ready}; use actix_web::dev::Payload; use actix_web::Error; // ... (DbClient, Mailer, User, NewUser, UserService Definitionen wie oben) ... // Unser benutzerdefinierter UserService-Extrahierer pub struct MyInjectedUserService(pub Arc<UserService>); // Halte Arc zum Dienst impl FromRequest for MyInjectedUserService { type Error = Error; type Future = Ready<Result<Self, Self::Error>>; fn from_request(req: &actix_web::HttpRequest, _: &mut Payload) -> Self::Future { // Wir können hier auf Anwendungsdaten zugreifen if let Some(user_service_arc) = req.app_data::<web::Data<UserService>>() { ready(Ok(MyInjectedUserService(user_service_arc.into_inner()))) } else { // Dieser Fall sollte idealerweise nicht eintreten, wenn app_data korrekt konfiguriert ist ready(Err(actix_web::error::ErrorInternalServerError( "UserService nicht in Anwendungsdaten gefunden", ))) } } } // Ein Handler, der den benutzerdefinierten Extrahierer verwendet #[get("/custom_extracted_users/{user_id}")] async fn get_user_with_custom_extractor_actix( user_id: web::Path<Uuid>, InjectedUserService(user_service): MyInjectedUserService, // Verwende unseren benutzerdefinierten Extrahierer ) -> impl Responder { println!( "Bearbeite get_user_with_custom_extractor_actix für ID: {}", user_id ); match user_service.get_user_by_id(user_id.into_inner()).await { Some(user) => HttpResponse::Ok().json(user), None => HttpResponse::NotFound().body("Benutzer nicht gefunden"), } } // Fügen Sie diese Route zur App-Konfiguration von main() hinzu: // .service(get_user_with_custom_extractor_actix) ## Vorteile von Dependency Injection * **Testbarkeit:** Durch das Injizieren von Abhängigkeiten können Sie reale Implementierungen während des Testens einfach durch Mock-Objekte ersetzen, was Unit- und Integrationstests erheblich vereinfacht und zuverlässiger macht. * **Modularität und lose Kopplung:** Komponenten sind weniger von den internen Implementierungsdetails ihrer Abhängigkeiten abhängig. Sie "kennen" nur die Schnittstellen (Traits) dessen, was sie benötigen, was eine bessere Trennung von Belangen fördert. * **Wartbarkeit:** Änderungen an der Implementierung einer Abhängigkeit erfordern keine Änderungen in jeder Komponente, die sie verwendet, solange die Schnittstelle gleich bleibt. Dies reduziert die Angriffsfläche für Fehler. * **Flexibilität und Wiederverwendbarkeit:** Dienste können für verschiedene Umgebungen (Entwicklung, Staging, Produktion) unterschiedlich konfiguriert oder in verschiedenen Kontexten ohne Änderung wiederverwendet werden. * **Klarheit:** Es ist sofort ersichtlich, welche Abhängigkeiten eine Komponente benötigt, indem man sich ihren Konstruktor- oder Handlerargumente ansieht. ## Fazit Dependency Injection ist ein entscheidendes Entwurfsmuster für die Erstellung skalierbarer, testbarer und wartbarer Anwendungen in Rust, insbesondere innerhalb von Webframeworks wie Axum und Actix Web. Durch die Nutzung von gemeinsam genutztem Anwendungszustand (Axums `State`, Actix Webs `web::Data`) und benutzerdefinierten Extrahierern können Entwickler externe Ressourcen und Service-Abhängigkeiten elegant verwalten, was zu saubereren Codebasen und einem expliziteren Kontrollfluss führt. Die Beherrschung dieser Dependency-Injection-Strategien wird Ihre Fähigkeit, robuste und anpassungsfähige Rust-Webdienste zu erstellen, erheblich verbessern.