Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Состояние бекенда

В прошлой главе мы научились создавать эндпоинты, которые работают только со значениями, полученными из запроса. Однако обычно в реальных бекенд приложениях существует некое состояние самого приложения, которое может содержать конфигурацию, пул соединений с базой данных, HTTP и GRPC клиенты для общения с другими сервисами, данные метрик, кеш и т.д.

Для работы с таким состоянием бэкенда в Axum существует обёртка State. Принцип работы следующий:

1) Мы создаём объект произвольной структуры, и добавляем его в конфигурацию роутера при помощи метода with_state.

struct AppState {
  // поля
}

#[tokio::main]
async fn main() {
    let my_app_data = Arc::new(AppState { ... });

    let app = Router::new()
        .route("/чего-то", get(обработчик))
        .with_state(my_app_data);
    ...
}

2) Далее, при помощи обёртки State этот объект можно будет заинжектить в любую функцию-обработчик, зарегистрированную в этом роутере.

async fn обработчик(State(my_app_data): State<Arc<AppState>>) -> Response { ... } 

Для примера создадим простой бекенд с эндпоинтом счётчиком. Каждый раз, когда мы будем делать GET запрос на http://localhost:8080/count, значение счётчика будет инкрементироваться, и в ответ мы будем получать строку, содержащую новое значение счётчика.

use std::sync::{ Arc, atomic::{AtomicU64, Ordering} };
use axum::{Router, extract::State, routing::get};

// Эта структура будет определять состояние нашего бэкенда
struct AppState {
    counter: AtomicU64,
}

#[tokio::main]
async fn main() {
    let shared_state = Arc::new(AppState {
        counter: AtomicU64::new(0),
    });

    let app = Router::new()
        .route("/count", get(tick_counter))
        .with_state(shared_state);
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn tick_counter(State(state): State<Arc<AppState>>) -> String {
    let prev_value = state.counter.fetch_add(1, Ordering::Relaxed);
    format!("New value: {}", prev_value + 1)
}

Первый переход на http://localhost:8080/count должен отобразить “New value: 1”, второй “New value: 2” и т.д.


У вас может возникнуть логичный вопрос: а почему бы просто не использовать глобальную переменную counter? Использование State имеет несколько преимуществ:

  • Это нагляднее, так как сразу понятно, что данные относятся к состоянию приложения
  • Это облегчает тестирование эндпоинтов
  • В Axum приложение может иметь несколько роутеров, и для каждого из них можно указать свой объект состояния, что мы увидим в следующей главе