Волга
Главная
API Docs
GitHub
  • English
  • Русский
Главная
API Docs
GitHub
  • English
  • Русский
  • Главная
  • Основы

    • Быстрый старт
    • Параметры маршрута
    • Параметры запроса
    • Группировка маршрутов
    • Заголовки (Headers)
  • Форматы данных

    • Работа с JSON
    • Работа с Form Data
    • Работа с файлами
    • Server-Sent Events (SSE)
  • Протоколы

    • HTTP/1 and HTTP/2
    • HTTPS
    • WebSockets & WebTransport
  • Продвинутые сценарии

    • Пользовательские Middleware
    • Сжатие ответов
    • Распаковка запросов
    • Центральный обработчик ошибок
    • Внедрение Зависимостей
    • Логгирование и Трассировка
    • Статические файлы
    • CORS (Cross-Origin Resource Sharing)
    • Cookies
    • Отмена запросов
    • Пользовательская обработка методов HEAD, OPTIONS и TRACE

Внедрение Зависимостей

Волга поддерживает мощный механизм внедрения зависимостей (Dependency Injection, DI) с тремя жизненными циклами: Singleton, Scoped и Transient.

Если вы не используете функцию full, то, включите функцию di, либо переключитесь на full в вашем Cargo.toml:

[dependencies]
volga = { version = "0.4.5", features = ["di"] }

Жизненные циклы зависимостей

Singleton

Singleton обеспечивает создание и использование единственного экземпляра зависимости на протяжении всего жизненного цикла вашего веб-приложения. Этот экземпляр потокобезопасен и может использоваться одновременно в разных обработчиках.

Пример: Singleton

use volga::{App, di::Dc, ok, not_found};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

#[derive(Clone, Default)]
struct InMemoryCache {
    inner: Arc<Mutex<HashMap<String, String>>>,
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Регистрация Singleton-сервиса
    app.add_singleton(InMemoryCache::default());

    // Использование общего экземпляра в обработчиках маршрутов
    app.map_get("/user/{id}", |id: String, cache: Dc<InMemoryCache>| async move {
        let user = cache.inner.lock().unwrap().get(&id);
        match user {
            Some(user) => ok!(user),
            None => not_found!("Пользователь не найден"),
        }
    });

    app.map_post("/user/{id}/{name}", |id: String, name: String, cache: Dc<InMemoryCache>| async move {
        cache.inner.lock().unwrap().insert(id, name);
        ok!()
    });

    app.run().await
}

В этом примере:

  • Метод add_singleton регистрирует InMemoryCache как Singleton.
  • Экстрактор Dc<T> разрешает зависимости по мере необходимости.
  • Dc<T> работает аналогично другим экстракторам, таким как Json<T> или Query<T>.

Инфо

Тип T должен реализовывать типажи Send, Sync и Default, если он не зависит от других объектов или используется готовый экземпляр.

Scoped

Scoped зависимость создает новый экземпляр для каждого HTTP-запроса. Экземпляр существует только в течение обработки запроса, обеспечивая изоляцию между запросами.

Пример: Scoped

use volga::{App, di::Dc, ok, not_found};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

#[derive(Clone, Default)]
struct InMemoryCache {
    inner: Arc<Mutex<HashMap<String, String>>>,
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Регистрация Scoped-сервиса
    app.add_scoped::<InMemoryCache>();

    // Использование отдельного экземпляра для каждого запроса
    app.map_get("/user/{id}", |id: String, cache: Dc<InMemoryCache>| async move {
        let user = cache.inner.lock().unwrap().get(&id);
        match user {
            Some(user) => ok!(user),
            None => not_found!("Пользователь не найден"),
        }
    });

    app.map_post("/user/{id}/{name}", |id: String, name: String, cache: Dc<InMemoryCache>| async move {
        cache.inner.lock().unwrap().insert(id, name);
        ok!()
    });

    app.run().await
}

Основные отличия от Singleton:

  • Метод add_scoped::<T>() регистрирует зависимость, которая создается для каждого HTTP-запроса.
  • Каждый запрос имеет свой уникальный экземпляр InMemoryCache.

Transient

Transient зависимость создает новый экземпляр при каждом запросе к контейнеру, независимо от контекста или запроса. Регистрация осуществляется с помощью метода add_transient::<T>(). Поведение похоже на Scoped, но экземпляр создается при каждом внедрении зависимости.

Совет

Реализуя Default вручную, вы можете управлять поведением создания экземпляров для Scoped и Transient зависимостей, а для более сложных сценариев используйте типаж Inject.

Использование DI в middleware

Чтобы внедрить зависимость в middleware, используйте метод resolve::<T>(), либо resolve_shared::<T> структуры HttpContext. Основное различие между ними заключается в том, что первый метод требует реализации типажа Clone для T, тогда как последний просто возвращает Arc<T>.

app.use_middleware(|ctx: HttpContext, next: Next| async move {
    let cache = ctx.resolve::<InMemoryCache>()?;
    // Выполнить действия...
    next(ctx).await
});

Итог

  • Singleton: Общий экземпляр на весь жизненный цикл приложения.
  • Scoped: Новый экземпляр для каждого HTTP-запроса.
  • Transient: Новый экземпляр при каждом запросе к контейнеру.

Более сложные примеры можно найти здесь.

Last Updated:
Prev
Центральный обработчик ошибок
Next
Логгирование и Трассировка