Step 1 연습문제: 오류처리 설계 철학과 분류 체계


학습 목표 달성 확인

이 연습문제들을 통해 1단계에서 학습한 내용을 실제로 적용해보세요.


연습문제 1: 오류 분류 실습

상황

다음은 실제 항공 발권 시스템에서 발생할 수 있는 오류 상황들입니다. 각각을 Domain/Application/Infrastructure 중 어느 계층에 속하는지 분류하고, 그 이유를 설명하세요.

오류 시나리오

  1. 시나리오 A: 사용자가 이미 만석인 항공편에 예약을 시도했습니다.

    • 분류: ?
    • 이유: ?
    • 적절한 예외 클래스명: ?
  2. 시나리오 B: 결제 진행 중 외부 결제 게이트웨이가 500 Internal Server Error를 반환했습니다.

    • 분류: ?
    • 이유: ?
    • 적절한 예외 클래스명: ?
  3. 시나리오 C: 데이터베이스 연결 풀이 고갈되어 커넥션을 얻을 수 없습니다.

    • 분류: ?
    • 이유: ?
    • 적절한 예외 클래스명: ?
  4. 시나리오 D: 사용자가 이미 취소된 예약 건에 대해 체크인을 시도했습니다.

    • 분류: ?
    • 이유: ?
    • 적절한 예외 클래스명: ?
  5. 시나리오 E: Redis 캐시 서버가 응답하지 않아 세션 정보를 가져올 수 없습니다.

    • 분류: ?
    • 이유: ?
    • 적절한 예외 클래스명: ?

답안 작성 가이드

// 예시 답안 형식
// 1. 시나리오 A
분류: Domain Layer
이유: 좌석 부족은 비즈니스 규칙 위반으로, 도메인 전문가가 이해할 수 있는 비즈니스 개념
예외 클래스: InsufficientSeatsException(requestedSeats, availableSeats, flightId)

연습문제 2: Fail Fast vs Fail Safe 전략 적용

상황

항공 발권 시스템의 다음 컴포넌트들에 대해 Fail Fast 또는 Fail Safe 중 어떤 전략을 적용해야 하는지 결정하고 이유를 설명하세요.

컴포넌트들

  1. 입력 검증: 사용자가 입력한 여권 번호 형식 검증

    • 전략: Fail Fast / Fail Safe
    • 이유: ?
    • 구현 예시: ?
  2. 외부 항공사 API: 실시간 항공편 정보 조회

    • 전략: Fail Fast / Fail Safe
    • 이유: ?
    • 구현 예시: ?
  3. 이메일 알림: 예약 확정 이메일 발송

    • 전략: Fail Fast / Fail Safe
    • 이유: ?
    • 구현 예시: ?
  4. 결제 처리: 신용카드 결제 진행

    • 전략: Fail Fast / Fail Safe
    • 이유: ?
    • 구현 예시: ?
  5. 마일리지 적립: 예약 완료 후 마일리지 포인트 적립

    • 전략: Fail Fast / Fail Safe
    • 이유: ?
    • 구현 예시: ?

구현 예시 작성 가이드

// Fail Fast 예시
class PassportValidator {
    fun validate(passportNumber: String) {
        require(passportNumber.isNotBlank()) { "여권 번호는 필수입니다" }
        require(passportNumber.matches(PASSPORT_PATTERN)) { "여권 번호 형식이 올바르지 않습니다" }
    }
}
 
// Fail Safe 예시
@CircuitBreaker(name = "email", fallbackMethod = "fallbackSendEmail")
fun sendBookingConfirmationEmail(booking: Booking) {
    emailService.sendConfirmation(booking)
}
 
fun fallbackSendEmail(booking: Booking, ex: Exception) {
    emailQueue.enqueue(booking) // 나중에 재시도
    logger.warn("Email sending failed, queued for retry", ex)
}

연습문제 3: Error Translation Pattern 구현

상황

현재 회사에서 연동하고 있는 외부 서비스 하나를 선택해서 Error Translation Pattern을 적용해보세요.

요구사항

  1. 외부 서비스 선택: 실제 연동 중인 서비스 (예: 결제 게이트웨이, 항공사 API, SMS 서비스 등)
  2. 오류 종류 파악: 해당 서비스에서 발생할 수 있는 오류들 나열
  3. 번역 규칙 정의: 각 외부 오류를 어떤 도메인 오류로 번역할지 결정
  4. 코드 구현: ErrorTranslator 클래스 구현

작성 가이드

/**
 * [서비스명] 오류 번역기
 * TODO: 실제 서비스명으로 변경
 */
@Component
class [ServiceName]ErrorTranslator {
    
    fun translate[ServiceName]Error(exception: Exception): BookingDomainException {
        return when (exception) {
            // TODO: 실제 외부 서비스의 예외들로 변경
            is [ExternalServiceException1] -> {
                // TODO: 적절한 도메인 예외로 번역
                BookingDomainException.[DomainException](...)
            }
            
            is [ExternalServiceException2] -> {
                // TODO: 다른 도메인 예외로 번역  
                BookingDomainException.[DomainException](...)
            }
            
            else -> {
                BookingDomainException.ExternalServiceException(
                    message = "TODO: 사용자 친화적인 메시지",
                    originalException = exception
                )
            }
        }
    }
}

제출할 내용

  1. 선택한 외부 서비스: ?
  2. 발생 가능한 오류들: ?
  3. 번역 규칙: ?
  4. 구현 코드: ?

연습문제 4: 중복 오류 메시지 문제 분석

상황

현재 겪고 있는 “MSA 환경에서 노운 이슈 전파” 문제를 구체적으로 분석해보세요.

분석 항목

  1. 문제 상황 기술

    • 어떤 오류가 어떤 경로로 전파되고 있는지?
    • 각 서비스에서 오류 메시지가 어떻게 변화하는지?
  2. 현재 문제점

    • 중복되는 정보는 무엇인지?
    • 손실되는 정보는 무엇인지?
    • 디버깅이 어려운 이유는?
  3. 개선 방안

    • Error Translation Pattern을 어떻게 적용할지?
    • 각 서비스 경계에서 어떤 번역이 필요한지?

작성 예시

현재 상황:
Service A (주문) → Service B (재고) → Service C (외부 API)

오류 전파 경로:
1. Service C: "EXTERNAL_API_RATE_LIMIT_EXCEEDED_429_RETRY_60s"
2. Service B: "InventoryServiceException: External API rate limited (원인: EXTERNAL_API_RATE_LIMIT_EXCEEDED_429_RETRY_60s)"  
3. Service A: "OrderProcessingException: Unable to check inventory (원인: InventoryServiceException...)"

문제점:
- 기술적 세부사항이 비즈니스 계층까지 노출됨
- 오류 메시지가 계속 중첩되어 길어짐
- 사용자에게 의미 없는 기술적 오류 코드 노출

개선 방안:
TODO: Error Translation Pattern 적용 계획 작성

연습문제 5: Error Context 설계

상황

디버깅과 모니터링을 위해 수집해야 할 오류 컨텍스트를 설계해보세요.

요구사항

현재 시스템에서 오류 발생 시 다음 정보들을 수집하는 ErrorContext 클래스를 설계하세요:

  1. 분산 추적 정보: traceId, spanId 등
  2. 요청 정보: endpoint, HTTP method, user agent 등
  3. 사용자 정보: userId, userType (개인정보 제외)
  4. 시스템 정보: 서비스 버전, 환경, 호스트명
  5. 비즈니스 컨텍스트: 예약ID, 항공편ID 등 도메인 식별자

구현 가이드

data class ErrorContext(
    // TODO: 분산 추적 정보
    val traceId: String,
    val spanId: String?,
    
    // TODO: 요청 정보
    val endpoint: String,
    val httpMethod: String,
    val userAgent: String?,
    val clientIp: String?,
    
    // TODO: 사용자 정보 (민감정보 제외)
    val userId: String?,
    val userType: UserType?,
    
    // TODO: 시스템 정보
    val serviceVersion: String,
    val environment: String,
    val hostname: String,
    
    // TODO: 비즈니스 컨텍스트
    val businessContext: Map<String, Any>,
    
    // TODO: 오류 정보
    val errorType: String,
    val errorMessage: String?,
    val rootCause: String?,
    
    // TODO: 타임스탬프
    val timestamp: Instant
)
 
@Component  
class ErrorContextCollector {
    
    fun collectContext(
        exception: Exception,
        request: HttpServletRequest
    ): ErrorContext {
        // TODO: 구현
    }
    
    private fun extractBusinessContext(exception: Exception): Map<String, Any> {
        // TODO: 예외에서 비즈니스 컨텍스트 추출
    }
}

연습문제 6: Datadog 메트릭 설계

상황

Datadog을 사용해 오류를 모니터링하기 위한 커스텀 메트릭을 설계해보세요.

요구사항

  1. 오류 발생률 추적: 도메인별, 심각도별 오류 카운터
  2. 응답 시간 추적: 오류 처리 소요 시간
  3. 외부 서비스 오류 추적: 서비스별 오류율
  4. 비즈니스 임팩트 추적: 예약 실패율, 결제 실패율 등

구현 가이드

@Component
class ErrorMetricsCollector(
    private val meterRegistry: MeterRegistry
) {
    
    fun recordDomainError(exception: BookingDomainException) {
        // TODO: 도메인 오류 메트릭 수집
        Counter.builder("booking.domain.errors")
            .tag("error.code", exception.errorCode.code)
            .tag("error.category", exception.errorCode.category.name)
            .tag("error.severity", exception.errorCode.severity.name)
            .register(meterRegistry)
            .increment()
    }
    
    fun recordApplicationError(exception: BookingApplicationException) {
        // TODO: 애플리케이션 오류 메트릭 수집
    }
    
    fun recordInfrastructureError(exception: BookingInfrastructureException) {
        // TODO: 인프라 오류 메트릭 수집
    }
    
    fun recordErrorResolutionTime(errorType: String, durationMs: Long) {
        // TODO: 오류 처리 시간 메트릭 수집
    }
    
    fun recordBusinessImpact(operation: String, success: Boolean) {
        // TODO: 비즈니스 임팩트 메트릭 수집
    }
}

자가 진단 체크리스트

각 연습문제를 완료한 후 다음 항목들을 확인해보세요:

이론 이해

  • Exception Handling의 4대 원칙을 설명할 수 있다
  • DDD 3계층 오류 분류를 실제 예시에 적용할 수 있다
  • Fail Fast vs Fail Safe를 상황에 맞게 선택할 수 있다
  • Anti-Corruption Layer의 필요성을 이해한다

실무 적용

  • 현재 시스템의 오류들을 3계층으로 분류할 수 있다
  • 외부 서비스 오류를 도메인 오류로 번역할 수 있다
  • 적절한 오류 컨텍스트를 수집할 수 있다
  • 모니터링을 위한 메트릭을 설계할 수 있다

코드 품질

  • 예외 클래스명이 명확하고 일관성이 있다
  • 오류 메시지가 사용자 친화적이다
  • 로깅 레벨이 적절하게 설정되었다
  • 메트릭 태그가 분석에 유용하다

다음 단계 준비

연습문제를 완료했다면, 2단계 “Exception Hierarchy 재설계”로 넘어갈 준비가 되었습니다.

2단계에서는:

  • 실제 Exception 클래스 계층 구조 설계
  • Error Code 체계 구축
  • 메시지 국제화(i18n) 처리
  • 직렬화/역직렬화 고려사항

을 다룰 예정입니다.

연습문제에서 어려웠던 부분이나 추가로 궁금한 점이 있다면 언제든 질문해주세요!

관련 문서