(refactor) more clean architecture
This commit is contained in:
parent
0a45d6e139
commit
8a03bdee96
8 changed files with 278 additions and 53 deletions
165
Cargo.lock
generated
165
Cargo.lock
generated
|
|
@ -63,6 +63,30 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"untrusted 0.7.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.6"
|
||||
|
|
@ -127,6 +151,26 @@ version = "1.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
|
|
@ -170,9 +214,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -193,6 +248,26 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
|
|
@ -331,6 +406,12 @@ version = "0.15.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
|
|
@ -446,6 +527,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
|
|
@ -551,6 +638,12 @@ dependencies = [
|
|||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
|
|
@ -914,12 +1007,31 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
|
|
@ -936,6 +1048,7 @@ version = "10.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d119c6924272d16f0ab9ce41f7aa0bfef9340c00b0bb7ca3dd3b263d4a9150b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"base64",
|
||||
"getrandom 0.2.16",
|
||||
"js-sys",
|
||||
|
|
@ -961,6 +1074,16 @@ version = "0.2.177"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
|
|
@ -1043,6 +1166,12 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
|
|
@ -1118,6 +1247,16 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
|
|
@ -1356,6 +1495,16 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
|
|
@ -1527,7 +1676,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"untrusted 0.9.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
|
|
@ -1551,6 +1700,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
|
|
@ -1595,7 +1750,7 @@ checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
|||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2410,6 +2565,12 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
|||
serde_json = "1.0"
|
||||
|
||||
|
||||
jsonwebtoken = "10.1"
|
||||
jsonwebtoken = { version = "10.1", features = ["aws_lc_rs"] }
|
||||
base64 = "0.22"
|
||||
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
sqlx-cli
|
||||
openssl
|
||||
|
||||
pkg-config # Помогает Cargo находить системные библиотеки
|
||||
zlib # Часто требуется для компиляции OpenSSL
|
||||
pkg-config
|
||||
zlib
|
||||
];
|
||||
shellHook = ''
|
||||
export OPENSSL_DIR="${pkgs.openssl.dev}"
|
||||
|
|
|
|||
28
src/db.rs
Normal file
28
src/db.rs
Normal 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")
|
||||
}
|
||||
68
src/main.rs
68
src/main.rs
|
|
@ -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
24
src/route.rs
Normal 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
40
src/route/handlers.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
0
tests/banking_integration.rs
Normal file
0
tests/banking_integration.rs
Normal file
Loading…
Add table
Add a link
Reference in a new issue