Микросервисы на Rust: архитектура и практика для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Rust для микросервисов?

Микросервисная архитектура уже давно стала стандартом для построения масштабируемых распределённых систем. Обычно для её реализации выбирают Go, Java или Node.js. Однако в последние годы всё больше внимания привлекает Rust — язык системного программирования, который сочетает производительность C++ с безопасностью памяти. В этой статье на pythonlib.ru мы разберём, как создавать микросервисы на Rust, какие библиотеки использовать и как организовать архитектуру.

Rust идеально подходит для микросервисов благодаря:

  • Отсутствию сборщика мусора (низкая задержка и предсказуемое потребление памяти);
  • Безопасной работе с памятью на этапе компиляции;
  • Отличной поддержке асинхронности через экосистему Tokio;
  • Малому размеру итогового бинарного файла (идеально для контейнеризации).

Архитектура микросервиса на Rust

Типичный микросервис на Rust состоит из нескольких слоёв:

  • HTTP-сервер (обычно на базе actix-web или axum);
  • Бизнес-логика (чистые функции Rust);
  • Слой данных (работа с БД через sqlx или diesel);
  • Асинхронный рантайм (Tokio);
  • Сериализация (чаще всего serde для JSON).

Рассмотрим базовую архитектуру на примере простого сервиса управления пользователями.

1. Настройка проекта и зависимости

Создайте новый проект Cargo и добавьте зависимости в 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. Создание основной структуры сервера

Определим модели данных и запустим HTTP-сервер:

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 хранилище для примера
async 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
}

Это минимальный микросервис. Обратите внимание: #[actix_web::main] — это макрос, который запускает асинхронный рантайм Tokio.

3. Асинхронная работа с базой данных

Настоящий микросервис должен сохранять данные. Используем sqlx для PostgreSQL. Создадим файл .env с URL подключения:

DATABASE_URL=postgres://user:password@localhost/users_db

Теперь напишем функцию для получения пользователей из БД:

use sqlx::PgPool;

async fn get_users_from_db(pool: web::Data<PgPool>) -> impl Responder {
    // Выполняем SQL-запрос и маппим результат в структуру 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::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let pool = PgPool::connect(&database_url).await.unwrap();

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .route("/users", web::get().to(get_users_from_db))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

В этом примере пул соединений передаётся через web::Data, что гарантирует безопасное совместное использование состояния.

Практические рекомендации по архитектуре

При построении микросервисов на Rust стоит учитывать несколько важных моментов:

1. Разделение на крейты (crates)

Для больших проектов разбивайте код на несколько крейтов в одном workspace:

  • user-service-core — модели и бизнес-логика;
  • user-service-api — HTTP-слой;
  • user-service-db — работа с базой данных.

Это улучшает переиспользование кода и ускоряет компиляцию.

2. Использование промежуточного ПО (middleware)

Actix-web поддерживает middleware для логирования, авторизации и CORS. Пример логирования запросов:

use actix_web::middleware::Logger;

HttpServer::new(|| {
    App::new()
        .wrap(Logger::default())
        .route("/users", web::get().to(get_users))
})

3. Graceful shutdown и health checks

Добавьте эндпоинт /health для проверки состояния сервиса:

async fn health_check() -> impl Responder {
    web::Json(serde_json::json!({"status": "ok"}))
}

// В main:
.route("/health", web::get().to(health_check))

Для graceful shutdown используйте сигналы ОС (SIGTERM, SIGINT) — Tokio обрабатывает их через tok

Блоги

Рекомендаций по книгам