rust db connect with axum

agnusdei·2025년 3월 23일
0

두 함수 db_connect_db_connect는 동일한 목적(데이터베이스 연결 풀 생성)을 가지고 있지만, 에러 처리 방식에서 큰 차이가 있습니다. 이 차이를 분석하고, Rust에서 Resultasync/await를 사용하는 방법에 대해 설명하겠습니다.


1. 두 함수의 차이점 분석

db_connect_

pub async fn db_connect_() -> PgPool {
    dotenv().ok();
    let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let pool = PgPoolOptions::new().connect(&url).await.expect("Failed to connect to the database");

    info!("Connected to the database");
    pool
}
  • 에러 처리: expect를 사용하여 에러 발생 시 프로그램을 즉시 종료합니다.
    • std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"): 환경 변수가 없으면 프로그램이 패닉(panic) 상태로 종료됩니다.
    • PgPoolOptions::new().connect(&url).await.expect("Failed to connect to the database"): 데이터베이스 연결 실패 시 프로그램이 패닉 상태로 종료됩니다.
  • 반환 타입: PgPool을 직접 반환합니다.
  • 장점:
    • 코드가 간단하고 직관적입니다.
  • 단점:
    • 에러 발생 시 프로그램이 종료되므로, 프로덕션 코드에서는 사용하기에 적합하지 않습니다.
    • 호출자가 에러를 처리할 수 없습니다.

db_connect

pub async fn db_connect() -> Result<PgPool, sqlx::Error> {
    dotenv().ok();
    let url = std::env::var("DATABASE_URL").map_err(|e| {
        tracing::error!("Failed to read DATABASE_URL: {}", e);
        sqlx::Error::Configuration(e.into())
    })?;

    let pool = PgPoolOptions::new()
        .connect(&url)
        .await
        .map_err(|e| {
            tracing::error!("Failed to connect to the database: {}", e);
            e
        })?;

    info!("Connected to the database");
    Ok(pool)
}
  • 에러 처리: Result를 사용하여 에러를 호출자에게 전달합니다.
    • std::env::var("DATABASE_URL").map_err(...): 환경 변수가 없으면 에러를 변환하고 로깅합니다.
    • PgPoolOptions::new().connect(&url).await.map_err(...): 데이터베이스 연결 실패 시 에러를 변환하고 로깅합니다.
  • 반환 타입: Result<PgPool, sqlx::Error>를 반환합니다.
  • 장점:
    • 호출자가 에러를 처리할 수 있습니다.
    • 프로그램이 예기치 않게 종료되지 않습니다.
    • 로깅을 통해 에러 발생 시 디버깅이 용이합니다.
  • 단점:
    • 코드가 조금 더 복잡해집니다.

2. Rust에서 Result 처리 방법

Result란?

  • Result는 Rust에서 에러 처리를 위한 열거형(enum)입니다.
  • Result<T, E>는 성공 시 Ok(T)를, 실패 시 Err(E)를 반환합니다.

주요 메서드

  1. unwrap:

    • ResultOk인 경우 값을 반환하고, Err인 경우 패닉을 발생시킵니다.
    • 예: let url = std::env::var("DATABASE_URL").unwrap();
  2. expect:

    • unwrap과 동일하지만, 패닉 메시지를 지정할 수 있습니다.
    • 예: let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
  3. ? 연산자:

    • ResultOk인 경우 값을 반환하고, Err인 경우 현재 함수에서 빠져나가 에러를 전파합니다.
    • 예: let url = std::env::var("DATABASE_URL")?;
  4. map_err:

    • ResultErr인 경우 에러를 변환합니다.
    • 예: let url = std::env::var("DATABASE_URL").map_err(|e| sqlx::Error::Configuration(e.into()))?;

3. async/awaitResult

async/await란?

  • async는 비동기 함수를 정의할 때 사용합니다.
  • await는 비동기 함수의 결과를 기다릴 때 사용합니다.

Result와 함께 사용

  • 비동기 함수에서 Result를 반환할 수 있습니다.
  • await?를 함께 사용하여 비동기 작업의 에러를 처리할 수 있습니다.

예:

pub async fn db_connect() -> Result<PgPool, sqlx::Error> {
    let url = std::env::var("DATABASE_URL")?; // 동기 작업
    let pool = PgPoolOptions::new().connect(&url).await?; // 비동기 작업
    Ok(pool)
}

4. 에러 처리 패턴

패턴 1: unwrap 또는 expect

  • 빠른 프로토타이핑이나 테스트 코드에서 사용합니다.
  • 프로덕션 코드에서는 사용을 지양합니다.
let url = std::env::var("DATABASE_URL").unwrap();

패턴 2: ? 연산자

  • 에러를 호출자에게 전파합니다.
  • 프로덕션 코드에서 권장됩니다.
let url = std::env::var("DATABASE_URL")?;

패턴 3: map_err

  • 에러를 변환하거나 로깅합니다.
  • 프로덕션 코드에서 유용합니다.
let url = std::env::var("DATABASE_URL").map_err(|e| {
    tracing::error!("Failed to read DATABASE_URL: {}", e);
    sqlx::Error::Configuration(e.into())
})?;

5. 결론

  • db_connect_:

    • 간단하지만, 프로덕션 코드에서는 사용하지 않는 것이 좋습니다.
    • expect를 사용하여 에러 발생 시 프로그램이 종료됩니다.
  • db_connect:

    • Result를 사용하여 에러를 호출자에게 전파합니다.
    • map_err를 사용하여 에러를 변환하고 로깅합니다.
    • 프로덕션 코드에서 권장되는 방식입니다.
  • Rust에서의 에러 처리:

    • Result를 사용하여 에러를 명시적으로 처리합니다.
    • ? 연산자를 사용하여 에러를 전파합니다.
    • map_err를 사용하여 에러를 변환하거나 로깅합니다.
  • async/await:

    • 비동기 작업을 쉽게 처리할 수 있습니다.
    • await?를 함께 사용하여 비동기 작업의 에러를 처리합니다.

이를 통해 Rust에서 안정적이고 유지보수 가능한 코드를 작성할 수 있습니다! 🚀

profile
DevSecOps ⚙️ + Pentest 🚩

0개의 댓글