AWS Lambda + API Gateway에서 409 Conflict 예외 처리

Sue·2025년 6월 11일
post-thumbnail

⚠️ AWS Lambda + API Gateway에서 409 Conflict 예외 처리


409 Conflict란?

클라이언트의 요청이 서버의 현재 상태와 충돌(conflict)하여 처리할 수 없을 때 사용하는 HTTP 상태 코드입니다.

주로 아래와 같은 경우에 사용됩니다:

상황예시
중복된 리소스 생성같은 사용자 아래 동일한 프로젝트 title 생성 요청
상태 충돌동시에 동일 리소스를 업데이트하는 경우 등

우리가 구현할 시나리오: 중복 프로젝트 생성 차단

  • 요청한 title을 가진 프로젝트가 이미 존재하면,
  • 프로젝트를 생성하지 않고 409 Conflict 상태 코드로 응답합니다.

구현 흐름 요약

  1. 프로젝트 생성 요청 전에 중복 여부 확인 쿼리 실행
  2. 이미 존재하면 → ConflictError 발생
  3. 에러 핸들러에서 상태 코드 409 및 응답 메시지 구성

Step 1: 중복 검사 함수 정의

def is_project_title_duplicate(cursor, owner_id: str, title: str) -> bool:
    query = """
        SELECT 1
        FROM projects
        WHERE owner_id = %s AND title = %s AND is_deleted = false
        LIMIT 1
    """
    cursor.execute(query, (owner_id, title))
    return cursor.fetchone() is not None
  • 필요한 조건만 단순하게 체크
  • LIMIT 1을 걸어 성능 최적화
  • 이미 soft delete된 건 무시

Step 2: ConflictError 예외 클래스 정의

from http import HTTPStatus

class ConflictError(Exception):
    def __init__(self, message: str = "요청 충돌이 발생했습니다."):
        self.message = message
        self.status_code = HTTPStatus.CONFLICT

    def to_dict(self):
        return {
            "status": "Failed",
            "message": f"[Conflict] {self.message}"
        }
  • 응답 포맷을 명세에 맞춰 구성 (status + message)
  • 상태 코드도 함께 반환할 수 있게 설계

Step 3: 핸들러에서 중복 검사 및 예외 발생

def create_project_handler(event):
    try:
        user = get_user_from_event(event)
        body = json.loads(event["body"])
        title = body.get("title")

        if not title:
            raise ValueError("title 값이 필요합니다.")

        with get_db_connection() as conn:
            with conn.cursor() as cursor:

                if is_project_title_duplicate(cursor, user.id, title):
                    raise ConflictError("동일한 이름의 프로젝트가 이미 존재합니다.")

                # 프로젝트 생성 쿼리 실행
                insert_query = """
                    INSERT INTO projects (owner_id, title)
                    VALUES (%s, %s)
                """
                cursor.execute(insert_query, (user.id, title))
                conn.commit()

        return {
            "statusCode": 201,
            "body": json.dumps({"status": "Success"})
        }

    except ConflictError as ce:
        return {
            "statusCode": ce.status_code,
            "body": json.dumps(ce.to_dict())
        }

    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({
                "status": "Failed",
                "message": "Internal server error"
            })
        }
  • 중복 검사 → 예외 발생 → 일관된 에러 응답
  • 모든 흐름이 명확하게 구분되어 유지보수도 쉬움

✅ 기대 응답

{
  "status": "Failed",
  "message": "[Conflict] 동일한 이름의 프로젝트가 이미 존재합니다."
}
  • HTTP 응답 코드: 409 Conflict

테스트 팁

  1. "테스트 프로젝트"로 정상적으로 한 번 생성
  2. 같은 사용자로 동일한 이름으로 다시 생성 요청
  3. 응답에서 "status": "Failed" + "409 Conflict" 코드가 오면 성공

보너스: DB에 UNIQUE 제약이 있는 경우

projects 테이블에서 owner_id + title을 UNIQUE 제약으로 잡았다면,
사전 검사 대신 IntegrityError를 잡고 ConflictError로 변환하는 방식도 가능

예:

try:
    cursor.execute(insert_query, (user.id, title))
except psycopg2.IntegrityError:
    raise ConflictError("이미 존재하는 프로젝트입니다.")

요약 정리

단계설명
중복 확인 함수 정의is_project_title_duplicate()
커스텀 예외 클래스 정의ConflictError
핸들러 사전 검사INSERT 전에 검사 → 중복이면 예외 발생
profile
AI/ML Engineer

0개의 댓글