Introduction : pourquoi Rust pour les microservices ?
L'architecture microservices est devenue la norme pour les systèmes distribués modernes. Cependant, le choix du langage de programmation pour implémenter des microservices est une décision critique. Rust offre une combinaison unique de performances de C/C++ et de sécurité mémoire, ce qui en fait un candidat idéal pour les services à forte charge.
Dans cet article, nous allons voir comment construire un microservice en Rust en utilisant les frameworks populaires Actix-web et Tokio. Vous découvrirez les principes architecturaux, le traitement asynchrone des requêtes et obtiendrez des exemples de code fonctionnels.
Architecture d'un microservice en Rust
Principes de base
Un microservice en Rust est généralement construit autour des composants suivants :
- Serveur HTTP — traitement des requêtes entrantes (Actix-web, Warp, Axum).
- Runtime asynchrone — gestion de la concurrence (Tokio, async-std).
- Logique métier — modules isolés avec des limites claires.
- Stockage de données — interaction avec la base de données via SQLx ou Diesel.
- Interaction inter-services — HTTP/gRPC ou files de messages (RabbitMQ, Kafka).
Structure du projet
Structure recommandée pour un microservice en Rust :
my_microservice/├── Cargo.toml├── src/│ ├── main.rs # Point d'entrée, démarrage du serveur│ ├── routes/ # Définition des routes│ ├── handlers/ # Gestionnaires de requêtes│ ├── models/ # Structures de données (DTO, entités)│ ├── services/ # Logique métier│ ├── repositories/ # Couche d'accès aux données│ └── config/ # Configuration (env, fichiers)└── tests/ # Tests d'intégrationExemple pratique : service de raccourcissement de liens
Considérons la création d'un microservice simple pour raccourcir des URL. Il acceptera des liens longs et retournera des identifiants courts.
Étape 1 : Configuration du projet
Créez un nouveau projet Cargo et ajoutez les dépendances dans Cargo.toml :
[package]name = "url-shortener"version = "0.1.0"edition = "2021"
[dependencies]actix-web = "4"actix-rt = "2"serde = { version = "1", features = ["derive"] }serde_json = "1"tokio = { version = "1", features = ["full"] }redis = { version = "0.23", features = ["tokio-comp"] }uuid = { version = "1", features = ["v4"] }dotenv = "0.15"env_logger = "0.10"log = "0.4"Étape 2 : Modèles de données
Définissons les structures de requête et de réponse :
// models/mod.rsuse serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]pub struct CreateUrlRequest { pub url: String,}
#[derive(Debug, Serialize)]pub struct CreateUrlResponse { pub short_code: String, pub original_url: String,}
#[derive(Debug, Serialize)]pub struct ErrorResponse { pub error: String,}Étape 3 : Couche service
Implémentons la logique métier en utilisant Redis pour le stockage :
// services/url_service.rsuse redis::AsyncCommands;use uuid::Uuid;
pub struct UrlService { redis_client: redis::Client,}
impl UrlService { pub fn new(redis_client: redis::Client) -> Self { Self { redis_client } }
pub async fn create_short_url(&self, original_url: &str) -> Result<String, String> { let short_code = Uuid::new_v4().to_string()[..8].to_string(); let mut conn = self.redis_client .get_multiplexed_async_connection() .await .map_err(|e| format!("Redis connection error: {}", e))?;
// Sauvegarde dans Redis : clé — short_code, valeur — original_url conn.set_ex(&short_code, original_url, 86400) // TTL 24 heures .await .map_err(|e| format!("Redis