Микросервисы на Rust: архитектура, практика и примеры кода

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

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

Начать курс

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

Микросервисная архитектура стала стандартом для современных распределённых систем. Однако выбор языка программирования для реализации микросервисов — критическое решение. Rust предлагает уникальное сочетание производительности C/C++ и безопасности памяти, что делает его идеальным кандидатом для высоконагруженных сервисов.

В этой статье мы разберём, как построить микросервис на Rust с использованием популярных фреймворков Actix-web и Tokio. Вы узнаете об архитектурных принципах, асинхронной обработке запросов и получите рабочие примеры кода.

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

Базовые принципы

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

  • HTTP-сервер — обработка входящих запросов (Actix-web, Warp, Axum).
  • Асинхронный рантайм — управление конкурентностью (Tokio, async-std).
  • Бизнес-логика — изолированные модули с чёткими границами.
  • Хранилище данных — взаимодействие с БД через SQLx или Diesel.
  • Межсервисное взаимодействие — HTTP/gRPC или очереди сообщений (RabbitMQ, Kafka).

Структура проекта

Рекомендуемая структура для микросервиса на Rust:

my_microservice/
├── Cargo.toml
├── src/
│   ├── main.rs          # Точка входа, запуск сервера
│   ├── routes/          # Определение маршрутов
│   ├── handlers/        # Обработчики запросов
│   ├── models/          # Структуры данных (DTO, сущности)
│   ├── services/        # Бизнес-логика
│   ├── repositories/    # Слой доступа к данным
│   └── config/          # Конфигурация (env, файлы)
└── tests/               # Интеграционные тесты

Практический пример: сервис сокращения ссылок

Рассмотрим создание простого микросервиса для сокращения URL. Он будет принимать длинные ссылки и возвращать короткие идентификаторы.

Шаг 1: Настройка проекта

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

Шаг 2: Модели данных

Определим структуры запроса и ответа:

// models/mod.rs
use 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,
}

Шаг 3: Сервисный слой

Реализуем бизнес-логику с использованием Redis для хранения:

// services/url_service.rs
use 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))?;

        // Сохраняем в Redis: ключ — short_code, значение — original_url
        conn.set_ex(&short_code, original_url, 86400) // TTL 24 часа
            .await
            .map_err(|e| format!("Redis set error: {}", e))?;

        Ok(short_code)
    }

    pub async fn get_original_url(&self, short_code: &str) -> Result<Option<String>, String> {
        let mut conn = self.redis_client
            .get_multiplexed_async_connection()
            .await
            .map_err(|e| format!("Redis connection error: {}", e))?;

        let url: Option<String> = conn
            .get(short_code)
            .await
            .map_err(|e| format!("Redis get error: {}", e))?;

        Ok(url)
    }
}

Шаг 4: HTTP-обработчики

Создадим маршруты и обработчики с помощью Actix-web:

// handlers/url_handler.rs
use actix_web::{web, HttpResponse, Responder};
use crate::models::{CreateUrlRequest, CreateUrlResponse, ErrorResponse};
use crate::services::url_service::UrlService;

pub async fn create_url(
    service: web::Data<UrlService>,
    body: web::Json<CreateUrlRequest>,
) -> impl Responder {
    match service.create_short_url(&body.url).await {
        Ok(short_code) => {
            let response = CreateUrlResponse {
                short_code,
                original_url: body.url.clone(),
            };
            HttpResponse::Created().json(response)
        }
        Err(err) => {
            let error = ErrorResponse { error: err };
            HttpResponse::InternalServerError().json(error)
        }
    }
}

pub async fn redirect_url(
    service: web::Data<UrlService>,
    path: web::Path<String>,
) -> impl Responder {
    let short_code = path.into_inner();
    match service.get_original_url(&short_code).await {
        Ok(Some(original_url)) => {
            HttpResponse::Found()
                .append_header(("Location", original_url))
                .finish()
        }
        Ok(None) => {
            let error = ErrorResponse {
                error: "URL not found".to_string(),
            };
            HttpResponse::NotFound().json(error)
        }
        Err(err) => {
            let error = ErrorResponse { error: err };
            HttpResponse::InternalServerError().json(error)
        }
    }
}

Шаг 5: Точка входа и маршрутизация

Соберём всё вместе в main.rs:

// main.rs
mod models;
mod handlers;
mod services;

use actix_web::{                

Блоги

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