Introduction : Pourquoi Rust pour les microservices ?
L'architecture microservices est depuis longtemps devenue la norme pour construire des systèmes distribués évolutifs. Habituellement, on choisit Go, Java ou Node.js pour sa mise en œuvre. Cependant, ces dernières années, Rust attire de plus en plus l'attention — un langage de programmation système qui combine les performances du C++ avec la sécurité mémoire. Dans cet article sur pythonlib.ru, nous allons voir comment créer des microservices en Rust, quelles bibliothèques utiliser et comment organiser l'architecture.
Rust est idéal pour les microservices grâce à :
- L'absence de ramasse-miettes (faible latence et consommation mémoire prévisible) ;
- La gestion sécurisée de la mémoire à la compilation ;
- L'excellent support de l'asynchrone via l'écosystème
Tokio; - La petite taille du binaire final (idéal pour la conteneurisation).
Architecture d'un microservice en Rust
Un microservice typique en Rust se compose de plusieurs couches :
- Serveur HTTP (généralement basé sur
actix-webouaxum) ; - Logique métier (fonctions Rust pures) ;
- Couche de données (interaction avec la base de données via
sqlxoudiesel) ; - Runtime asynchrone (
Tokio) ; - Sérialisation (le plus souvent
serdepour le JSON).
Examinons l'architecture de base à l'aide d'un exemple simple de service de gestion d'utilisateurs.
1. Configuration du projet et dépendances
Créez un nouveau projet Cargo et ajoutez les dépendances dans Cargo.toml :
[package]name = "user-service"version = "0.1.0"edition = "2021"
[dependencies]actix-web = "4"serde = { version = "1", features = ["derive"] }serde_json = "1"tokio = { version = "1", features = ["full"] }sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }dotenv = "0.15"2. Création de la structure de base du serveur
Définissons les modèles de données et lançons le serveur HTTP :
use actix_web::{web, App, HttpServer, Responder};use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]struct User { id: u32, name: String, email: String,}
// Stockage en mémoire pour l'exempleasync fn get_users() -> impl Responder { let users = vec![ User { id: 1, name: "Alice".into(), email: "alice@example.com".into() }, User { id: 2, name: "Bob".into(), email: "bob@example.com".into() }, ]; web::Json(users)}
#[actix_web::main]async fn main() -> std::io::Result<()> { println!("Starting user-service on http://127.0.0.1:8080");
HttpServer::new(|| { App::new() .route("/users", web::get().to(get_users)) }) .bind("127.0.0.1:8080")? .run() .await}Ceci est un microservice minimal. Notez que #[actix_web::main] est une macro qui lance le runtime asynchrone Tokio.
3. Interaction asynchrone avec la base de données
Un vrai microservice doit persister les données. Utilisons sqlx pour PostgreSQL. Créons un fichier .env avec l'URL de connexion :
DATABASE_URL=postgres://user:password@localhost/users_dbÉcrivons maintenant une fonction pour récupérer les utilisateurs depuis la base de données :
use sqlx::PgPool;
async fn get_users_from_db(pool: web::Data<PgPool>) -> impl Responder { // Exécute la requête SQL et mappe le résultat dans la structure User let users: Vec<User> = sqlx::query_as::<_, User>("SELECT id, name, email FROM users") .fetch_all(pool.get_ref()) .await .unwrap_or_default();
web::Json(users)}
#[actix_web::main]async fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); let database_url = std::env::va