(refactor) more clean architecture

This commit is contained in:
Rorik Star Platinum 2025-11-05 00:10:18 +03:00
parent 0a45d6e139
commit 8a03bdee96
8 changed files with 278 additions and 53 deletions

28
src/db.rs Normal file
View file

@ -0,0 +1,28 @@
// src/db.rs
// Почему это здесь?
// - Это всё, связанное с инициализацией и конфигурацией базы данных
// - Здесь создаётся connection pool, который переиспользуется во всём приложении
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use std::env;
/// Инициализирует PgPool (connection pool для PostgreSQL)
///
/// Connection pool — это набор переиспользуемых соединений к БД.
/// Вместо того, чтобы открывать новое соединение для каждого запроса,
/// мы берём готовое соединение из пула.
///
/// Это **критически важно** для производительности:
/// - Открытие соединения — медленная операция
/// - Connection pool решает эту проблему
pub async fn init_pool() -> PgPool {
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgPoolOptions::new()
.max_connections(5) // Максимум 5 одновременных соединений
.connect(&database_url)
.await
.expect("Failed to create Postgres connection pool")
}

View file

@ -1,64 +1,36 @@
// Подключаем модули напрямую без mod.rs!
mod banking {
pub mod client;
pub mod models;
pub mod error;
}
// src/main.rs
mod route; // Объявляем модуль route (это либо route.rs, либо route/mod.rs)
mod db; // Объявляем модуль для работы с БД
mod services {
pub mod account_service;
pub mod consent_service;
}
mod handlers {
pub mod health;
pub mod accounts;
}
mod config;
mod db;
mod error;
use axum::{
routing::get,
Router,
};
use std::net::SocketAddr;
use sqlx::PgPool;
/// Общее состояние приложения, передаётся во все handlers
/// Это паттерн Axum: State<AppState> используется в extract'ах
#[derive(Clone)]
pub struct AppState {
pub db_pool: PgPool,
}
#[tokio::main]
async fn main() {
// 1. Загружаем переменные окружения из .env или secrets (через sops)
dotenvy::dotenv().ok();
// 2. Инициализируем базу данных
let db_pool = db::init_pool().await;
let banking_clients = banking::client::init_all_banks().await;
println!("✅ Database connection pool created successfully.");
let app_state = AppState {
db_pool,
banking_clients,
};
// 3. Создаём общее состояние приложения
let app_state = AppState { db_pool };
let app = Router::new()
.route("/api/health", get(handlers::health::health_handler))
.route("/api/accounts/aggregated", get(handlers::accounts::get_aggregated_accounts))
.with_state(app_state);
// 4. Конфигурируем роуты (все роуты централизованы в route::router())
let app = route::router(app_state);
// 5. Запускаем сервер
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
println!("🚀 Server listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
#[derive(Clone)]
pub struct AppState {
pub db_pool: sqlx::PgPool,
pub banking_clients: BankingClients,
}
#[derive(Clone)]
pub struct BankingClients {
pub vbank: banking::client::BankClient,
pub abank: banking::client::BankClient,
pub sbank: banking::client::BankClient,
}

24
src/route.rs Normal file
View file

@ -0,0 +1,24 @@
// src/route.rs
// Почему это здесь?
// - Это центр управления всеми HTTP маршрутами (роутами)
// - Здесь объявляются все подмодули (handlers, будущие сервисы и т.д.)
// - Здесь собирается финальный Router для Axum
pub mod handlers; // Объявляем подмодуль handlers
use axum::{routing::get, Router};
use crate::AppState;
/// Создаёт и возвращает Router со всеми сконфигурированными роутами
/// Функция принимает AppState и передаёт его всем handlers'ам
pub fn router(app_state: AppState) -> Router {
Router::new()
// GET /api/health — health-check эндпоинт
.route("/api/health", get(handlers::health_handler))
// Сюда добавим новые роуты по мере разработки:
// .route("/api/accounts", get(handlers::accounts::get_accounts))
// .route("/api/payments", post(handlers::payments::create_payment))
.with_state(app_state)
}

40
src/route/handlers.rs Normal file
View file

@ -0,0 +1,40 @@
// src/route/handlers.rs
// Почему это здесь?
// - Это всё, что обрабатывает HTTP запросы
// - Каждый handler'а — это async функция, которая обрабатывает запрос и возвращает ответ
use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
Json,
};
use serde_json::json;
use crate::AppState;
/// Health-check handler
///
/// Что он делает:
/// 1. Принимает AppState через extract::State (это параметр, который Axum инжектирует)
/// 2. Пытается выполнить простой SELECT 1 в БД (проверка подключения)
/// 3. Возвращает 200 OK если всё хорошо, или 500 если БД недоступна
pub async fn health_handler(
State(state): State<AppState>,
) -> impl IntoResponse {
// Пытаемся выполнить простой запрос к БД
let result = sqlx::query("SELECT 1")
.execute(&state.db_pool)
.await;
// Обрабатываем результат
match result {
Ok(_) => (
StatusCode::OK,
Json(json!({ "status": "Database connection is successful." })),
).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "status": format!("Database connection failed: {}", e) })),
).into_response(),
}
}