ActixMiddleTechnical
Как Actix-web интегрируется с асинхронными драйверами баз данных, такими как sqlx?
Создайте PgPool через PgPoolOptions::new().connect() и передайте его в App как web::Data. В хендлерах принимайте pool: web::Data<sqlx::PgPool> и используйте sqlx::query_as! или query! для асинхронных запросов.
Интеграция Actix-web с sqlx
Actix-web не имеет встроенной ORM — для работы с PostgreSQL, MySQL или SQLite используется sqlx, асинхронная библиотека с проверкой запросов на этапе компиляции. Пул соединений передаётся через механизм web::Data.
Зависимости (Cargo.toml)
// Cargo.toml
[dependencies]
actix-web = "4"
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-native-tls", "uuid", "chrono", "macros"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
uuid = { version = "1", features = ["serde", "v4"] }
Создание пула и передача в приложение
use actix_web::{web, App, HttpServer, HttpResponse, Error};
use sqlx::postgres::PgPoolOptions;
use serde::Serialize;
#[derive(Serialize, sqlx::FromRow)]
struct User {
id: i32,
name: String,
email: String,
}
async fn list_users(pool: web::Data<sqlx::PgPool>) -> Result<HttpResponse, Error> {
let users = sqlx::query_as::<_, User>(
"SELECT id, name, email FROM users ORDER BY id"
)
.fetch_all(pool.get_ref())
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok().json(users))
}
#[actix_web::main]
async fn main() -> std::io::Result<> {
let database_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
let pool = PgPoolOptions::new()
.max_connections(10)
.connect(&database_url)
.await
.expect("Failed to create pool");
// Применяем миграции из папки ./migrations
sqlx::migrate!("./migrations")
.run(&pool)
.await
.expect("Migration failed");
let pool_data = web::Data::new(pool);
HttpServer::new(move || {
App::new()
.app_data(pool_data.clone())
.route("/users", web::get().to(list_users))
})
.bind("0.0.0.0:8080")?
.run()
.await
}
Использование compile-time query! макроса
Макрос sqlx::query! проверяет SQL на этапе компиляции, если установлена переменная окружения DATABASE_URL или используется sqlx prepare:
async fn get_user(
pool: web::Data<sqlx::PgPool>,
path: web::Path<i32>,
) -> Result<HttpResponse, Error> {
let user_id = path.into_inner();
let rec = sqlx::query!(
"SELECT id, name, email FROM users WHERE id = $1",
user_id
)
.fetch_optional(pool.get_ref())
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
match rec {
Some(row) => Ok(HttpResponse::Ok().json(serde_json::json!({
"id": row.id,
"name": row.name,
"email": row.email,
}))),
None => Ok(HttpResponse::NotFound().finish()),
}
}
Транзакции
async fn transfer_funds(
pool: web::Data<sqlx::PgPool>,
body: web::Json<Transfer>,
) -> Result<HttpResponse, Error> {
let mut tx = pool.begin().await
.map_err(actix_web::error::ErrorInternalServerError)?;
sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2",
body.amount, body.from_id)
.execute(&mut *tx).await
.map_err(actix_web::error::ErrorInternalServerError)?;
sqlx::query!("UPDATE accounts SET balance = balance + $1 WHERE id = $2",
body.amount, body.to_id)
.execute(&mut *tx).await
.map_err(actix_web::error::ErrorInternalServerError)?;
tx.commit().await
.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok().finish())
}
Подводные камни
- Неправильный размер пула — по умолчанию PgPool создаёт до 10 соединений; под нагрузкой запросы будут ждать свободного слота. Устанавливайте
max_connectionsв соответствии с лимитом БД. - pool.get_ref() vs &**pool — оба варианта корректны, но смешение может запутать читателей; придерживайтесь одного стиля в проекте.
- DATABASE_URL на этапе компиляции —
sqlx::query!требует живую БД или кэшsqlx-data.json(создаётся черезcargo sqlx prepare) в CI. - Незакрытые транзакции — если обработчик вернул ошибку до
tx.commit(), транзакция откатывается при drop, но только если не игнорируются ошибки между запросами. - N+1 запросы — sqlx не предоставляет lazy loading; при вложенных данных легко написать цикл запросов. Используйте JOIN или
fetch_allс ручной сборкой. - Хаотичная обработка ошибок —
sqlx::Errorсодержит много вариантов; оборачивая черезErrorInternalServerError, вы теряете детали. Лучше использовать кастомный тип ошибки сResponseError. - Миграции в production —
sqlx::migrate!()вmain()удобен для разработки, но в production может вызвать проблемы при горизонтальном масштабировании — запускайте миграции отдельным шагом деплоя.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.