Introduction: Why Rust for Microservices?
Microservice architecture has long become the standard for building scalable distributed systems. Usually, Go, Java, or Node.js are chosen for its implementation. However, in recent years, Rust — a systems programming language that combines C++ performance with memory safety — has been attracting more and more attention. In this article on pythonlib.ru, we will analyze how to create microservices in Rust, which libraries to use, and how to organize the architecture.
Rust is ideal for microservices due to:
- No garbage collector (low latency and predictable memory consumption);
- Safe memory management at compile time;
- Excellent async support through the
Tokioecosystem; - Small final binary size (ideal for containerization).
Microservice Architecture in Rust
A typical microservice in Rust consists of several layers:
- HTTP server (usually based on
actix-weboraxum); - Business logic (pure Rust functions);
- Data layer (working with DB via
sqlxordiesel); - Async runtime (
Tokio); - Serialization (most often
serdefor JSON).
Let's look at the basic architecture using a simple user management service as an example.
1. Project Setup and Dependencies
Create a new Cargo project and add dependencies to 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. Creating the Main Server Structure
Let's define data models and start the HTTP server:
use actix_web::{web, App, HttpServer, Responder};use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]struct User { id: u32, name: String, email: String,}
// In-memory storage for exampleasync 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}This is a minimal microservice. Note: #[actix_web::main] is a macro that starts the Tokio async runtime.
3. Async Database Operations
A real microservice should persist data. Let's use sqlx for PostgreSQL. Create a .env file with the connection URL:
DATABASE_URL=postgres://user:password@localhost/users_dbNow let's write a function to get users from the database:
use sqlx::PgPool;
async fn get_users_from_db(pool: web::Data<PgPool>) -> impl Responder { // Execute SQL query and map the result to the User struct 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