feat(presentation): NatsEventPublisher with no-op fallback when NATS_URL unset

This commit is contained in:
2026-05-14 10:00:39 +02:00
parent 2e702c64cc
commit 3318635da6
3 changed files with 26 additions and 11 deletions

View File

@@ -14,6 +14,8 @@ api-types = { workspace = true }
postgres = { workspace = true } postgres = { workspace = true }
postgres-search = { workspace = true } postgres-search = { workspace = true }
auth = { workspace = true } auth = { workspace = true }
nats = { workspace = true }
async-nats = { workspace = true }
axum = { workspace = true } axum = { workspace = true }
sqlx = { workspace = true } sqlx = { workspace = true }
tower-http = { workspace = true } tower-http = { workspace = true }

View File

@@ -5,23 +5,36 @@ pub mod routes;
pub mod state; pub mod state;
use std::sync::Arc; use std::sync::Arc;
use sqlx::PgPool;
use state::AppState;
use postgres_search::PgSearchRepository;
use async_trait::async_trait; use async_trait::async_trait;
use sqlx::PgPool;
use domain::{errors::DomainError, events::DomainEvent, ports::EventPublisher}; use domain::{errors::DomainError, events::DomainEvent, ports::EventPublisher};
use postgres_search::PgSearchRepository;
use state::AppState;
struct NoOpEventPublisher; struct NoOpEventPublisher;
#[async_trait] #[async_trait]
impl EventPublisher for NoOpEventPublisher { impl EventPublisher for NoOpEventPublisher {
async fn publish(&self, _e: &DomainEvent) -> Result<(), DomainError> { async fn publish(&self, _e: &DomainEvent) -> Result<(), DomainError> { Ok(()) }
Ok(())
}
} }
pub fn build_state(pool: PgPool, jwt_secret: String) -> AppState { pub async fn build_state(pool: PgPool, jwt_secret: String) -> AppState {
let event_publisher: Arc<dyn EventPublisher> = match std::env::var("NATS_URL") {
Ok(url) => match async_nats::connect(&url).await {
Ok(client) => {
tracing::info!("Connected to NATS at {url}");
Arc::new(nats::NatsEventPublisher::new(client))
}
Err(e) => {
tracing::warn!("Failed to connect to NATS at {url}: {e} — using no-op publisher");
Arc::new(NoOpEventPublisher)
}
},
Err(_) => {
tracing::info!("NATS_URL not set — using no-op event publisher");
Arc::new(NoOpEventPublisher)
}
};
AppState { AppState {
users: Arc::new(postgres::user::PgUserRepository::new(pool.clone())), users: Arc::new(postgres::user::PgUserRepository::new(pool.clone())),
thoughts: Arc::new(postgres::thought::PgThoughtRepository::new(pool.clone())), thoughts: Arc::new(postgres::thought::PgThoughtRepository::new(pool.clone())),
@@ -38,6 +51,6 @@ pub fn build_state(pool: PgPool, jwt_secret: String) -> AppState {
search: Arc::new(PgSearchRepository::new(pool.clone())), search: Arc::new(PgSearchRepository::new(pool.clone())),
auth: Arc::new(auth::JwtAuthService::new(jwt_secret, 86400 * 30)), auth: Arc::new(auth::JwtAuthService::new(jwt_secret, 86400 * 30)),
hasher: Arc::new(auth::Argon2PasswordHasher), hasher: Arc::new(auth::Argon2PasswordHasher),
events: Arc::new(NoOpEventPublisher), events: event_publisher,
} }
} }

View File

@@ -16,7 +16,7 @@ async fn main() {
let pool = PgPool::connect(&database_url).await.expect("DB connect failed"); let pool = PgPool::connect(&database_url).await.expect("DB connect failed");
sqlx::migrate!("../adapters/postgres/migrations").run(&pool).await.expect("Migrations failed"); sqlx::migrate!("../adapters/postgres/migrations").run(&pool).await.expect("Migrations failed");
let state = presentation::build_state(pool, jwt_secret); let state = presentation::build_state(pool, jwt_secret).await;
let app = presentation::routes::router() let app = presentation::routes::router()
.with_state(state) .with_state(state)
.layer(CorsLayer::permissive()); .layer(CorsLayer::permissive());