Affix State – Gemeinsame Daten in Anfragen

Die Affix-State-Middleware wird verwendet, um gemeinsame Daten zum Depot hinzuzufügen. Um die Affix-State-Funktion zu nutzen, müssen Sie das Feature affix-state in der Cargo.toml aktivieren.

Funktionsanalyse

Affix State bietet eine einfache Möglichkeit, Daten während der Anfrageverarbeitung zu teilen. Es ermöglicht Ihnen:

  • Globale Konfigurationen oder gemeinsame Daten während der Routenkonfiguration einzufügen
  • Auf diese Daten über das Depot in jedem Handler zuzugreifen
  • Jeden klonbaren Typ als Zustandsdaten zu unterstützen

Vergleich mit anderen Frameworks zum schnellen Konzeptverständnis

FrameworkSpracheAnsatz zur Zustandsverwaltung
Salvo (Affix State)RustSpeichert und greift über Depot zu, unterstützt mehrere Typen
AxumRustSpeichert Zustand über Extension, ähnlich aber mit anderer Verwendung
Actix-webRustNutzt App Data und Web::Data zum Teilen von Zuständen
GinGoVerwendet context.Set und context.Get zum Speichern und Abrufen von Daten
EchoGoNutzt context.Set und context.Get zur Verwaltung gemeinsamer Zustände
SpringJavaVerwendet ApplicationContext oder @Bean-Annotationen zur Abhängigkeitsverwaltung
QuarkusJavaNutzt CDI und Dependency-Injection-Mechanismen
Express.jsJavaScriptVerwendet app.locals oder req.app.locals zum Speichern globaler Zustände
Nest.jsJavaScriptNutzt ein Dependency-Injection-System zur Verwaltung gemeinsamer Dienste
Koa.jsJavaScriptVerwendet ctx.state zum Speichern von anfragebezogenen Zuständen

Typische Anwendungsfälle

  • Gemeinsame Nutzung von Datenbankverbindungspools
  • Teilen von Anwendungskonfigurationen
  • Gemeinsame Nutzung von Cache-Instanzen
  • Teilen von API-Clients
  • Globale Zähler oder Zustandsverfolgung

Der Vorteil von Affix State liegt in seiner Einfachheit und Flexibilität, die es ermöglicht, jeden Datentyp problemlos über verschiedene Routen und Handler hinweg zu teilen, ohne umfangreichen Boilerplate-Code. Beispielcode

main.rs
Cargo.toml
use std::sync::Arc;
use std::sync::Mutex;

use salvo::prelude::*;

// Configuration structure with username and password
#[allow(dead_code)]
#[derive(Default, Clone, Debug)]
struct Config {
    username: String,
    password: String,
}

// State structure to hold a list of fail messages
#[derive(Default, Debug)]
struct State {
    fails: Mutex<Vec<String>>,
}

#[handler]
async fn hello(depot: &mut Depot) -> String {
    // Obtain the Config instance from the depot
    let config = depot.obtain::<Config>().unwrap();
    // Get custom data from the depot
    let custom_data = depot.get::<&str>("custom_data").unwrap();
    // Obtain the shared State instance from the depot
    let state = depot.obtain::<Arc<State>>().unwrap();
    // Lock the fails vector and add a new fail message
    let mut fails_ref = state.fails.lock().unwrap();
    fails_ref.push("fail message".into());
    // Format and return the response string
    format!("Hello World\nConfig: {config:#?}\nFails: {fails_ref:#?}\nCustom Data: {custom_data}")
}

#[tokio::main]
async fn main() {
    // Initialize the tracing subscriber for logging
    tracing_subscriber::fmt().init();

    // Create a Config instance with default username and password
    let config = Config {
        username: "root".to_string(),
        password: "pwd".to_string(),
    };

    // Set up the router with state injection and custom data
    let router = Router::new()
        // Use hoop to inject middleware and data into the request context
        .hoop(
            affix_state::inject(config)
                // Inject a shared State instance into the request context
                .inject(Arc::new(State {
                    fails: Mutex::new(Vec::new()),
                }))
                // Insert custom data into the request context
                .insert("custom_data", "I love this world!"),
        )
        // Register the hello handler for the root path
        .get(hello)
        // Add an additional route for the path "/hello" with the same handler
        .push(Router::with_path("hello").get(hello));

    // Bind the server to port 8698 and start serving
    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}