Aufbau robuster und performanter REST-APIs mit Axum, Actix Web und Diesel
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Einleitung
In der sich schnell entwickelnden Landschaft der Webentwicklung nimmt die Nachfrage nach hochperformanten, zuverlässigen und wartbaren APIs stetig zu. Egal, ob Sie ein Backend für eine moderne Webanwendung, einen mobilen Dienst oder eine Microservice-Architektur erstellen, die Wahl des Technologiestacks hat erheblichen Einfluss auf den Erfolg Ihres Projekts. Rust hat sich mit seinem unübertroffenen Fokus auf Leistung, Speichersicherheit und Nebenläufigkeit als überzeugende Wahl für die Backend-Entwicklung herauskristallisiert. Dieser Artikel befasst sich damit, wie wir zwei der beliebtesten – und oft diskutierten – Web-Frameworks von Rust, Axum und Actix Web, zusammen mit dem leistungsstarken ORM (Object-Relational Mapper) Diesel, nutzen können, um REST-APIs zu erstellen, die nicht nur blitzschnell sind, sondern auch eine außergewöhnliche Typsicherheit und Wartbarkeit aufweisen.
Die praktische Bedeutung dieser Kombination ist tiefgreifend. Leistungsintensive Anwendungen profitieren von den nahezu null Overheads von Rust und den Compile-Time-Garantien, wodurch Laufzeitfehler minimiert und der Durchsatz maximiert wird. Die von Diesel durchgesetzte Typsicherheit führt zu weniger Fehlern bei Datenbankinteraktionen, da Dateninkonsistenzen bereits zur Kompilierungszeit erkannt werden, anstatt in der Produktion zu eskalieren. Darüber hinaus bietet das robuste Ökosystem dieser Bibliotheken Entwicklern die Werkzeuge, die sie benötigen, um komplexe, skalierbare Systeme mit Zuversicht zu erstellen. Wir werden die Kernkonzepte hinter jeder dieser Technologien untersuchen, ihre individuellen Stärken verstehen und dann demonstrieren, wie sie elegant zusammenarbeiten, um eine kohärente und leistungsstarke Backend-Lösung zu bilden.
Verständnis der Kernkomponenten
Bevor wir uns mit der Implementierung befassen, wollen wir ein klares Verständnis der Schlüsseltechnologien gewinnen, die wir verwenden werden: Axum, Actix Web und Diesel.
Axum
Axum ist ein Webanwendungsframework, das auf dem Tokio asynchronen Laufzeitsystem und der Hyper HTTP-Bibliothek aufbaut. Es wird von der Tokio-Community entwickelt und nutzt ein "macro-freies" Design, das das mächtige Typsystem von Rust für Routing und Middleware verwendet. Die Kernphilosophie von Axum dreht sich um Komponierbarkeit und die Einhaltung von Standard-Rust-Traits, wodurch es sich für Rustaceans sehr idiomatisch anfühlt. Zu den Hauptmerkmalen gehören:
- Typsicheres Routing: Routen werden als Funktionen definiert, die Extraktoren akzeptieren, welche Anforderungsdaten parsen und Abhängigkeiten auf typsichere Weise injizieren. Dies reduziert die Wahrscheinlichkeit von Laufzeitfehlern aufgrund falscher Datentypen.
- Minimalistisch und Komponierbar: Axum fördert den Aufbau von Anwendungen aus kleineren, komponierbaren Einheiten. Middleware kann beispielsweise problemlos auf einzelne Routen oder Routengruppen angewendet werden.
- Integration in das Tokio-Ökosystem: Als Teil des Tokio-Ökosystems profitiert Axum von Tokios robuster asynchroner Laufzeit, hervorragenden Nebenläufigkeitsprimitiven und reichhaltigen Werkzeugen.
Actix Web
Actix Web ist ein leistungsstarkes, pragmatisches und extrem schnelles Web-Framework für Rust. Es ist bekannt für sein aktor-basiertes Nebenläufigkeitsmodell, das hochgradig nebenläufige Anwendungen mit hervorragenden Leistungseigenschaften ermöglicht. Obwohl ein Aktorenmodell komplex klingen mag, bietet Actix Web eine High-Level-API, die seine Verwendung vereinfacht. Seine herausragenden Merkmale sind:
- Extreme Leistung: Actix Web gehört in verschiedenen Benchmarks durchweg zu den schnellsten Web-Frameworks. Dies liegt an seinem effizienten Aktorenmodell und seiner optimierten internen Architektur.
- Batteries Included: Actix Web bietet eine umfassende Reihe von Funktionen "out-of-the-box", darunter Routing, Middleware, Sitzungen, WebSockets und ein leistungsstarkes Testdienstprogramm.
- Aktor-basiertes Nebenläufigkeitsmodell (abstrahiert): Obwohl auf einem Aktorenmodell aufgebaut, interagieren Entwickler hauptsächlich mit
web::Service
undweb::Data
-Komponenten, die einen Großteil der zugrunde liegenden Komplexität abstrahieren und sich auf die gleichzeitige Verarbeitung von HTTP-Anfragen konzentrieren.
Diesel
Diesel ist ein leistungsstarkes und typsicheres ORM und ein Query Builder für Rust. Es zielt darauf ab, eine sichere und effiziente Möglichkeit zur Interaktion mit relationalen Datenbanken zu bieten. Die Hauptstärke von Diesel liegt in seinen Compile-Time-Garantien bezüglich der Gültigkeit des Datenbankschemas und der Abfragen.
- Typsicherheit zur Kompilierungszeit: Diesel generiert Rust-Typen, die direkt Ihrem Datenbankschema entsprechen. Das bedeutet, dass Ihr Code fehlschlägt, wenn Sie versuchen, eine Spalte abzufragen, die nicht existiert, oder einen Wert mit einem falschen Typ einzufügen. Dies verhindert viele häufige Laufzeitfehler.
- Leistungsstarker Query Builder: Diesel bietet eine ausdrucksstarke domänenspezifische Sprache (DSL) zum Erstellen komplexer SQL-Abfragen auf eine typsichere und idiomatische Rust-Weise.
- Migrationssystem: Diesel enthält ein robustes Migrationssystem, das hilft, die Entwicklung des Datenbankschemas über die Zeit hinweg zu verwalten.
- Unterstützung für mehrere Datenbanken: Diesel unterstützt PostgreSQL, MySQL und SQLite.
Warum diese kombinieren?
Die Schönheit dieser Kombination liegt in der Nutzung der Stärken jeder Komponente. Sowohl Axum als auch Actix Web eignen sich hervorragend für die Verarbeitung von HTTP-Anfragen und Routing, wobei Axum durch Extraktoren eine explizitere Typsicherheit bietet und Actix Web rohe Leistung und ein funktionsreiches Ökosystem bietet. Diesel bietet andererseits die entscheidende Schicht der typsicheren Interaktion mit der Datenbank, abstrahiert rohes SQL und behält gleichzeitig die Leistung bei. Diese Trennung der Zuständigkeiten führt zu wartbarem Code, bei dem die Web-Logik von der Datenzugriffslogik getrennt ist und beide durch das starke Typsystem von Rust unterstützt werden.
Erstellung einer einfachen REST-API: Benutzer und Beiträge
Lassen Sie uns veranschaulichen, wie eine einfache REST-API zur Verwaltung von Benutzern und ihren Beiträgen erstellt wird. Wir implementieren grundlegende CRUDOperationen für beide. Wir beginnen mit der Definition unseres Datenbankschemas, richten dann das Diesel ORM ein und integrieren es schließlich sowohl mit Axum als auch mit Actix Web.
Datenbank-Setup mit Diesel
Zuerst müssen wir eine Datenbank (z. B. PostgreSQL) einrichten. Wir verwenden Diesel CLI zur Verwaltung von Migrationen.
1. Installieren Sie Diesel CLI:
cargo install diesel_cli --no-default-features --features postgres
(Ersetzen Sie postgres
durch mysql
oder sqlite
, wenn Sie eine andere Datenbank verwenden).
2. Diesel initialisieren:
Erstellen Sie eine .env
-Datei mit Ihrer Datenbank-URL:
DATABASE_URL=postgres://user:password@localhost/your_database_name
Initialisieren Sie dann Diesel in Ihrem Projekt:
diesel setup
Dies erstellt ein migrations
-Verzeichnis.
3. Migrationen erstellen:
Definieren Sie unsere Tabellen users
und posts
.
diesel migration generate create_users
Bearbeiten Sie die generierte up.sql
-Datei:
-- migrations/timestamp_create_users/up.sql CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE );
diesel migration generate create_posts
Bearbeiten Sie die generierte up.sql
-Datei:
-- migrations/timestamp_create_posts/up.sql CREATE TABLE posts ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), title VARCHAR(255) NOT NULL, body TEXT NOT NULL, published BOOLEAN NOT NULL DEFAULT FALSE );
4. Migrationen ausführen:
diesel migration run
5. Diesel-Schema und Modelle generieren:
Fügen Sie diesel
und dotenvy
zu Ihrer Cargo.toml
hinzu:
[dependencies] diesel = { version = "2.1.0", features = ["postgres", "r2d2", "chrono"] } # Später auch Axum/Actix Web und serde, anyhow usw. hinzufügen
Führen Sie diesel print-schema
aus und kopieren Sie die Ausgabe in src/schema.rs
oder verwenden Sie diesel infer-schema > src/schema.rs
. Dies generiert die Rust-Darstellung Ihrer Tabellen.
// src/schema.rs // Diese Datei wird von Diesel CLI generiert. diesel::table! { posts (id) { id -> Int4, user_id -> Int4, title -> Varchar, body -> Text, published -> Bool, } } diesel::table! { users (id) { id -> Int4, username -> Varchar, email -> Varchar, } } diesel::joinable!(posts -> users (user_id)); diesel::allow_tables_to_appear_in_same_query!( posts, users, );
Als Nächstes definieren Sie Ihre Rust-Modelle und DTOs in src/models.rs
:
// src/models.rs use diesel::prelude::*; use serde::{Deserialize, Serialize}; use crate::schema::{users, posts}; #[derive(Queryable, Selectable, Debug, Serialize)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct User { pub id: i32, pub username: String, pub email: String, } #[derive(Insertable, Deserialize)] #[diesel(table_name = users)] pub struct NewUser { pub username: String, pub email: String, } #[derive(Queryable, Selectable, Debug, Serialize)] #[diesel(belongs_to(User))] #[diesel(table_name = posts)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Post { pub id: i32, pub user_id: i32, pub title: String, pub body: String, pub published: bool, } #[derive(Insertable, Deserialize)] #[diesel(table_name = posts)] pub struct NewPost { pub user_id: i32, pub title: String, pub body: String, pub published: Option<bool>, // Optional, defaults to false in DB } #[derive(Deserialize, Serialize)] pub struct UpdatePost { pub title: Option<String>, pub body: Option<String>, pub published: Option<bool>, }
Datenbankverbindungspooling
Für die Leistung verwenden wir r2d2
für das Verbindungspooling.
// src/db_config.rs use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool}; use std::env; pub type PgPool = Pool<ConnectionManager<PgConnection>>; pub fn establish_connection_pool() -> PgPool { dotenvy::dotenv().ok(); let database_url = env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); let manager = ConnectionManager::<PgConnection>::new(database_url); Pool::builder() .max_size(10) // Maximum number of connections in the pool .build(manager) .expect("Failed to create pool.") }
Implementierung von REST-Endpunkten mit Axum
Nun erstellen wir die API-Endpunkte mit Axum.
1. Cargo.toml
für Axum:
[dependencies] axum = { version = "0.7.5", features = ["macros"] } tokio = { version = "1.37.0", features = ["full"] } serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" diesel = { version = "2.1.0", features = ["postgres", "r2d2", "chrono"] } r2d2 = "0.8.10" anyhow = "1.0.83" dotenvy = "0.15.7"
2. Axum API-Handler in src/handlers_axum.rs
:
// src/handlers_axum.rs use axum::{ extract::{Path, State}, http::StatusCode, response::{IntoResponse, Json}, routing::{get, post, patch, delete}, Router, }; diesel::prelude::*; diesel::r2d2::{ConnectionManager, Pool}; use serde_json::json; use crate::models::{User, NewUser, Post, NewPost, UpdatePost}; use crate::schema::{users, posts}; use crate::db_config::PgPool; pub type DbConnection = diesel::r2d2::PooledConnection<ConnectionManager<PgConnection>>; // Hilfsfunktion zum Abrufen einer Verbindung aus dem Pool async fn get_conn(pool: &PgPool) -> Result<DbConnection, (StatusCode, String)> { pool.get().map_err(|e| { (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get DB connection: {}", e)) }) } // Benutzer-Handler pub async fn create_user( State(pool): State<PgPool>, Json(new_user): Json<NewUser>, ) -> Result<Json<User>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let user = diesel::insert_into(users::table) .values(&new_user) .get_result::<User>(&mut conn) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create user: {}", e)))?; Ok(Json(user)) } pub async fn get_all_users( State(pool): State<PgPool>, ) -> Result<Json<Vec<User>>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let users_list = users::table .load::<User>(&mut conn) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to fetch users: {}", e)))?; Ok(Json(users_list)) } pub async fn get_user_by_id( State(pool): State<PgPool>, Path(user_id): Path<i32>, ) -> Result<Json<User>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let user = users::table .find(user_id) .first::<User>(&mut conn) .map_err(|e| match e { diesel::result::Error::NotFound => (StatusCode::NOT_FOUND, "User not found".to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to get user: {}", e)), })?; Ok(Json(user)) } // Post-Handler pub async fn create_post( State(pool): State<PgPool>, Json(new_post): Json<NewPost>, ) -> Result<Json<Post>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let post = diesel::insert_into(posts::table) .values(&new_post) .get_result::<Post>(&mut conn) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create post: {}", e)))?; Ok(Json(post)) } pub async fn get_posts_by_user( State(pool): State<PgPool>, Path(user_id): Path<i32>, ) -> Result<Json<Vec<Post>>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let user_posts = Post::belonging_to(&users::table.find(user_id).first::<User>(&mut conn) .map_err(|e| match e { diesel::result::Error::NotFound => (StatusCode::NOT_FOUND, "User not found for posts".to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to fetch user for posts: {}", e)), })?) .load::<Post>(&mut conn) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to fetch posts: {}", e)))?; Ok(Json(user_posts)) } pub async fn update_post( State(pool): State<PgPool>, Path(post_id): Path<i32>, Json(update_data): Json<UpdatePost>, ) -> Result<Json<Post>, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let updated_post: Post = diesel::update(posts::table.find(post_id)) .set(( update_data.title.map(|t| posts::title.eq(t)), update_data.body.map(|b| posts::body.eq(b)), update_data.published.map(|p| posts::published.eq(p)), )) .get_result::<Post>(&mut conn) .map_err(|e| match e { diesel::result::Error::NotFound => (StatusCode::NOT_FOUND, "Post not found".to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to update post: {}", e)), })?; Ok(Json(updated_post)) } pub async fn delete_post( State(pool): State<PgPool>, Path(post_id): Path<i32>, ) -> Result<StatusCode, (StatusCode, String)> { let mut conn = get_conn(&pool).await?; let num_deleted = diesel::delete(posts::table.find(post_id)) .execute(&mut conn) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete post: {}", e)))?; if num_deleted == 0 { Err((StatusCode::NOT_FOUND, "Post not found".to_string())) } else { Ok(StatusCode::NO_CONTENT) } }
3. Hauptfunktion in src/main.rs
(Axum-Version):
// src/main.rs (Axum-Version) mod schema; mod models; mod db_config; mod handlers_axum; use axum::Router; use axum::routing::get; use crate::db_config::establish_connection_pool; use crate::handlers_axum::{ create_user, get_all_users, get_user_by_id, create_post, get_posts_by_user, update_post, delete_post }; #[tokio::main] async fn main() { let pool = establish_connection_pool(); let app = Router::new() .route("/users", get(get_all_users).post(create_user)) .route("/users/:user_id", get(get_user_by_id)) .route("/users/:user_id/posts", get(get_posts_by_user)) .route("/posts", post(create_post)) .route("/posts/:post_id", patch(update_post).delete(delete_post)) .with_state(pool); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); println!("Axum server listening on http://0.0.0.0:3000"); axum::serve(listener, app).await.unwrap(); }
Implementierung von REST-Endpunkten mit Actix Web
Nun erstellen wir dieselben API-Endpunkte mit Actix Web.
1. Cargo.toml
für Actix Web:
[dependencies] actix-web = "4.9.0" actix-rt = "2.10.0" # Für das `main`-Makro serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" diesel = { version = "2.1.0", features = ["postgres", "r2d2", "chrono"] } r2d2 = "0.8.10" anyhow = "1.0.83" dotenvy = "0.15.7"
2. Actix Web API-Handler in src/handlers_actix.rs
:
// src/handlers_actix.rs use actix_web::{web, HttpResponse, Responder}; diesel::prelude::*; diesel::r2d2::{ConnectionManager, Pool}; use serde_json::json; use crate::models::{User, NewUser, Post, NewPost, UpdatePost}; use crate::schema::{users, posts}; use crate::db_config::PgPool; pub type DbConnection = diesel::r2d2::PooledConnection<ConnectionManager<PgConnection>>; // Hilfsfunktion zum Abrufen einer Verbindung aus dem Pool async fn get_conn_actix(pool: web::Data<PgPool>) -> Result<DbConnection, HttpResponse> { web::block(move || pool.get()) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to get DB connection: {}", e)))? // Map actix_rt::SystemError .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to get DB connection: {}", e))) // Map r2d2::Error } // Benutzer-Handler pub async fn create_user_actix( pool: web::Data<PgPool>, new_user: web::Json<NewUser>, ) -> impl Responder { let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let user = web::block(move || { diesel::insert_into(users::table) .values(&new_user.into_inner()) .get_result::<User>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to create user: {}", e)))? // Map actix_rt::SystemError .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to create user: {}", e))); HttpResponse::Ok().json(user?) } pub async fn get_all_users_actix( pool: web::Data<PgPool>, ) -> impl Responder { let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let users_list = web::block(move || { users::table .load::<User>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to fetch users: {}", e)))? // Map actix_rt::SystemError .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to fetch users: {}", e))); HttpResponse::Ok().json(users_list?) } pub async fn get_user_by_id_actix( pool: web::Data<PgPool>, path: web::Path<i32>, ) -> impl Responder { let user_id = path.into_inner(); let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let user = web::block(move || { users::table .find(user_id) .first::<User>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to get user: {}", e)))? // Map actix_rt::SystemError .map_err(|e| match e { diesel::result::Error::NotFound => HttpResponse::NotFound().body("User not found"), _ => HttpResponse::InternalServerError().body(format!("Failed to get user: {}", e)), })?; HttpResponse::Ok().json(user) } // Post-Handler pub async fn create_post_actix( pool: web::Data<PgPool>, new_post: web::Json<NewPost>, ) -> impl Responder { let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let post = web::block(move || { diesel::insert_into(posts::table) .values(&new_post.into_inner()) .get_result::<Post>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to create post: {}", e)))? // Map actix_rt::SystemError .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to create post: {}", e))); HttpResponse::Ok().json(post?) } pub async fn get_posts_by_user_actix( pool: web::Data<PgPool>, path: web::Path<i32>, ) -> impl Responder { let user_id = path.into_inner(); let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let user_posts = web::block(move || { let user_found = users::table.find(user_id).first::<User>(&mut conn)?; Post::belonging_to(&user_found) .load::<Post>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to fetch posts: {}", e)))? // Map actix_rt::SystemError .map_err(|e| match e { diesel::result::Error::NotFound => HttpResponse::NotFound().body("User not found for posts"), _ => HttpResponse::InternalServerError().body(format!("Failed to fetch posts: {}", e)), })?; HttpResponse::Ok().json(user_posts) } pub async fn update_post_actix( pool: web::Data<PgPool>, path: web::Path<i32>, update_data: web::Json<UpdatePost>, ) -> impl Responder { let post_id = path.into_inner(); let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let updated_post: Post = web::block(move || { diesel::update(posts::table.find(post_id)) .set(( update_data.title.map(|t| posts::title.eq(t)), update_data.body.map(|b| posts::body.eq(b)), update_data.published.map(|p| posts::published.eq(p)), )) .get_result::<Post>(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to update post: {}", e)))? // Map actix_rt::SystemError .map_err(|e| match e { diesel::result::Error::NotFound => HttpResponse::NotFound().body("Post not found"), _ => HttpResponse::InternalServerError().body(format!("Failed to update post: {}", e)), })?; HttpResponse::Ok().json(updated_post) } pub async fn delete_post_actix( pool: web::Data<PgPool>, path: web::Path<i32>, ) -> impl Responder { let post_id = path.into_inner(); let mut conn = match get_conn_actix(pool).await { Ok(c) => c, Err(e) => return e, }; let num_deleted = web::block(move || { diesel::delete(posts::table.find(post_id)) .execute(&mut conn) }) .await .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to delete post: {}", e)))? // Map actix_rt::SystemError .map_err(|e| HttpResponse::InternalServerError().body(format!("Failed to delete post: {}", e))); if num_deleted == 0 { HttpResponse::NotFound().body("Post not found") } else { HttpResponse::NoContent().finish() } }
3. Hauptfunktion in src/main.rs
(Actix Web-Version):
// src/main.rs (Actix Web-Version) mod schema; mod models; mod db_config; mod handlers_actix; use actix_web::{web, App, HttpServer}; use crate::db_config::establish_connection_pool; use crate::handlers_actix::{ create_user_actix, get_all_users_actix, get_user_by_id_actix, create_post_actix, get_posts_by_user_actix, update_post_actix, delete_post_actix }; #[actix_web::main] async fn main() -> std::io::Result<()> { let pool = establish_connection_pool(); let pool_data = web::Data::new(pool); // Wrap pool in web::Data println!("Actix Web server listening on http://0.0.0.0:3000"); HttpServer::new(move || { App::new() .app_data(pool_data.clone()) // Pass shared data to routes .service( web::resource("/users") .route(web::get().to(get_all_users_actix)) .route(web::post().to(create_user_actix)) ) .service(web::resource("/users/{user_id}").route(web::get().to(get_user_by_id_actix))) .service(web::resource("/users/{user_id}/posts").route(web::get().to(get_posts_by_user_actix))) .service(web::resource("/posts").route(web::post().to(create_post_actix))) .service( web::resource("/posts/{post_id}") .route(web::patch().to(update_post_actix)) .route(web::delete().to(delete_post_actix)) ) }) .bind(("0.0.0.0", 3000))? .run() .await }
Hinweis: Um zwischen Axum und Actix Web zu wechseln, kommentieren Sie die main
-Funktion eines aus und kommentieren Sie die andere ein und passen Sie die Cargo.toml
-Abhängigkeiten entsprechend an.
Anwendungsszenario und Vorteile
Dieses Beispiel zeigt ein typisches REST-API-Backend für eine Blogging-Plattform oder ein Content-Management-System. Die wichtigsten beobachteten Vorteile:
- Leistung: Sowohl Axum als auch Actix Web, unterstützt durch Tokios asynchrones Laufzeitsystem, liefern eine außergewöhnliche Rohleistung für die Verarbeitung gleichzeitiger HTTP-Anfragen. Datenbankinteraktionen werden effizient über
r2d2
-Verbindungspooling verwaltet, wodurch Engpässe vermieden werden. - Typsicherheit: Die Überprüfungen zur Kompilierungszeit von Diesel stellen sicher, dass unsere API-Handler korrekt mit der Datenbank interagieren. Die Strukturelemente
NewUser
,User
,NewPost
,Post
undUpdatePost
entsprechen direkt den Datenbankoperationen und JSON-Payloads, wodurch Laufzeit-Typfehler minimiert werden. Jede Diskrepanz im Schema oder in der Abfragestruktur führt zu einem Kompilierungsfehler, wodurch viele Fehler frühzeitig erkannt werden. - Wartbarkeit: Die klare Trennung zwischen der Logik des Web-Frameworks (Routing, Anforderungsverarbeitung) und der Logik der Datenbankinteraktion (Diesel-Abfragen) macht die Codebasis organisiert und einfacher zu warten. Handler konzentrieren sich auf die Orchestrierung des Datenflusses, während Diesel die Feinheiten von SQL übernimmt.
- Skalierbarkeit: Die Speichereffizienz von Rust und die asynchrone Natur von Axum/Actix Web ermöglichen es diesen Diensten, eine große Anzahl gleichzeitiger Verbindungen mit minimalem Ressourcenverbrauch zu verarbeiten, was sie ideal für Anwendungen mit hohem Datenverkehr macht.
- Robustheit: Das Ownership-System und der Borrow Checker von Rust eliminieren gängige Fehlerklassen wie Nullpointer-Dereferenzen und Datenrennen, was zu robusteren und zuverlässigeren Diensten führt.
Fazit
Der Aufbau von REST-APIs mit Rust unter Verwendung von Axum oder Actix Web zusammen mit Diesel bietet eine leistungsstarke Kombination für Entwickler, die Leistung, Typsicherheit und Zuverlässigkeit suchen. Dieser Stack ermöglicht es Ihnen, Backend-Dienste zu erstellen, die nicht nur blitzschnell, sondern auch robust und angenehm zu warten sind. Indem die Mehrheit potenzieller Fehler zur Kompilierungszeit erfasst wird, heben Rust, Axum/Actix Web und Diesel gemeinsam den Standard dessen an, was Sie von Ihrer Backend-Infrastruktur erwarten können. Dieser synergistische Ansatz schöpft wirklich das Potenzial von Rust für die Webentwicklung aus und verschiebt die Grenzen dessen, was in Bezug auf Geschwindigkeit und Korrektheit erreichbar ist.