콘텐츠로 이동

에러 계층 구조

Spakky Framework의 에러 클래스 계층 구조와 사용법을 설명합니다. 모든 항목은 현재 core/*/srcplugins/*/src의 실제 에러 클래스를 기준으로 합니다.


개요

Spakky는 구조화된 에러 계층을 제공합니다. 모든 프레임워크 에러는 AbstractSpakkyFrameworkError를 상속하며, 각 패키지별로 전용 기반 에러 클래스가 있습니다.

flowchart TD
  Exception --> AbstractSpakkyFrameworkError

  AbstractSpakkyFrameworkError --> AbstractSpakkyApplicationError
  AbstractSpakkyFrameworkError --> AbstractSpakkyAOPError
  AbstractSpakkyFrameworkError --> AbstractSpakkyPodError
  AbstractSpakkyFrameworkError --> AbstractSpakkyDomainError
  AbstractSpakkyFrameworkError --> AbstractSpakkyPersistencyError
  AbstractSpakkyFrameworkError --> AbstractSpakkyExternalError
  AbstractSpakkyFrameworkError --> AbstractSpakkyEventError
  AbstractSpakkyFrameworkError --> AbstractSpakkyTaskError
  AbstractSpakkyFrameworkError --> AbstractSpakkyCacheError
  AbstractSpakkyFrameworkError --> AbstractSpakkyTracingError
  AbstractSpakkyFrameworkError --> AbstractSpakkyOutboxError
  AbstractSpakkyFrameworkError --> AbstractSpakkySagaError
  AbstractSpakkyFrameworkError --> AbstractSpakkyFastAPIError
  AbstractSpakkyFrameworkError --> AbstractSpakkySqlAlchemyError
  AbstractSpakkyFrameworkError --> AbstractSpakkyCeleryError
  AbstractSpakkyFrameworkError --> AbstractSpakkyOpenTelemetryError
  AbstractSpakkyFrameworkError --> AbstractSpakkyGrpcError
  AbstractSpakkyFrameworkError --> AbstractSpakkyLoggingError
  AbstractSpakkyFrameworkError --> SecurityErrors[spakky-security concrete errors]
  AbstractSpakkyFrameworkError --> CommonErrors[common concrete errors]

  AbstractSpakkyDomainError --> AbstractDomainValidationError
  AbstractSpakkyDomainError --> EntityNotFoundError
  AbstractSpakkyDomainError --> VersionConflictError
  AbstractSpakkyDomainError --> CannotMonkeyPatchEntityError
  AbstractSpakkyDomainError --> UnhashableFieldTypeError

  AbstractSpakkySagaError --> SagaFlowDefinitionError
  AbstractSpakkySagaError --> SagaCompensationFailedError
  AbstractSpakkySagaError --> SagaStepTimeoutError
  AbstractSpakkySagaError --> SagaParallelMergeConflictError
  AbstractSpakkySagaError --> SagaEngineNotConnectedError

  AbstractSpakkyCacheError --> InvalidCacheTTLError
  AbstractSpakkyCacheError --> CacheKeyGenerationError
  AbstractSpakkyCacheError --> AbstractSpakkyRedisError
  AbstractSpakkyRedisError --> RedisCacheOperationError
  AbstractSpakkyRedisError --> RedisCacheSerializationError

  AbstractSpakkySqlAlchemyError --> AbstractSpakkySqlAlchemyORMError
  AbstractSpakkySqlAlchemyError --> AbstractSpakkySqlAlchemyPersistencyError
  AbstractSpakkyGrpcError --> AbstractGrpcStatusError

기반 에러 클래스

AbstractSpakkyFrameworkError

모든 Spakky 프레임워크 에러의 최상위 기반 클래스입니다.

from spakky.core.common.error import AbstractSpakkyFrameworkError

class AbstractSpakkyFrameworkError(Exception, ABC):
    """모든 Spakky 프레임워크 에러의 기반 클래스"""

    message: ClassVar[str]  # 에러 메시지 (클래스 수준 또는 인스턴스 수준)

특징:

  • message 속성으로 사람이 읽을 수 있는 에러 메시지 제공
  • 클래스 수준 기본 메시지 또는 인스턴스별 커스텀 메시지 지원

코어 패키지 에러

spakky (코어)

AbstractSpakkyApplicationError

애플리케이션 부트스트랩 관련 에러입니다.

from spakky.core.application.error import AbstractSpakkyApplicationError
에러 설명
CannotDetermineScanPathError 스캔 경로를 자동 결정할 수 없음
CannotAssignSystemContextIDError 시스템 컨텍스트 ID 덮어쓰기 시도
ApplicationContextAlreadyStartedError 이미 시작된 컨텍스트를 재시작 시도
ApplicationContextAlreadyStoppedError 이미 중지된 컨텍스트를 재중지 시도
EventLoopThreadNotStartedInApplicationContextError 이벤트 루프 스레드 미시작 상태에서 접근
EventLoopThreadAlreadyStartedInApplicationContextError 이벤트 루프 스레드 중복 시작 시도

AbstractSpakkyPodError

Pod 등록 및 인스턴스화 관련 에러입니다.

from spakky.core.pod.error import AbstractSpakkyPodError
에러 설명
PodAnnotationFailedError Pod 어노테이션 처리 실패
PodInstantiationFailedError Pod 인스턴스 생성 실패
NegativeOrderValueError 음수 @Order 값 지정
QualifierSelectorNotCallableError @Qualifier selector가 호출 불가
CannotDeterminePodTypeError Pod 타입 추론 불가
CannotUseVarArgsInPodError *args/**kwargs 사용 금지
CannotUsePositionalOnlyArgsInPodError 위치 전용 인자 사용 금지
CannotUseOptionalReturnTypeInPodError Optional 반환 타입 금지 (함수 Pod)
UnexpectedDependencyNameInjectedError 예상치 못한 이름의 의존성 주입
UnexpectedDependencyTypeInjectedError 예상치 못한 타입의 의존성 주입

컨테이너 에러

from spakky.core.pod.interfaces.container import (
    CircularDependencyGraphDetectedError,
    NoSuchPodError,
    NoUniquePodError,
    CannotRegisterNonPodObjectError,
    PodNameAlreadyExistsError,
)
에러 설명
CircularDependencyGraphDetectedError 순환 의존성 감지
NoSuchPodError 요청한 Pod를 찾을 수 없음
NoUniquePodError 여러 후보 Pod 중 선택 불가
CannotRegisterNonPodObjectError @Pod 없는 객체 등록 시도
PodNameAlreadyExistsError Pod 이름 중복

공통 유틸리티 에러

from spakky.core.common.annotation import AnnotationNotFoundError, MultipleAnnotationFoundError
from spakky.core.common.error import GenericMROTypeError
from spakky.core.common.importing import CannotScanNonPackageModuleError
from spakky.core.common.metadata import InvalidAnnotatedTypeError, MetadataNotFoundError
에러 설명
GenericMROTypeError generic_mro()에 타입이 아닌 값 전달
CannotScanNonPackageModuleError 패키지가 아닌 모듈을 스캔 대상으로 지정
AnnotationNotFoundError 요청한 어노테이션 메타데이터 없음
MultipleAnnotationFoundError 동일 어노테이션이 여러 개 발견됨
MetadataNotFoundError Annotated 타입에서 기대 메타데이터 없음
InvalidAnnotatedTypeError 유효하지 않은 Annotated 타입 전달

AbstractSpakkyAOPError

AOP 관련 에러입니다.

from spakky.core.aop.error import AbstractSpakkyAOPError
에러 설명
AspectInheritanceError Aspect가 IAspect/IAsyncAspect 미구현

spakky-domain

도메인 모델 관련 에러입니다.

from spakky.domain.error import (
    AbstractSpakkyDomainError,
    AbstractDomainValidationError,
)
에러 설명
AbstractSpakkyDomainError 도메인 에러 기반 클래스
AbstractDomainValidationError 도메인 검증 에러 기반 클래스
CannotMonkeyPatchEntityError Entity 속성 직접 변경 시도
UnhashableFieldTypeError ValueObject 필드 타입이 해시 불가

spakky-event

이벤트 시스템 관련 에러입니다.

from spakky.event.error import (
    AbstractSpakkyEventError,
    InvalidMessageError,
    UnknownEventTypeError,
)
에러 설명
InvalidMessageError 잘못된 메시지 형식
UnknownEventTypeError Domain/Integration Event가 아닌 타입 발행

spakky-data

데이터 접근 관련 에러입니다.

from spakky.data.persistency.error import AbstractSpakkyPersistencyError
from spakky.data.persistency.repository import EntityNotFoundError, VersionConflictError
from spakky.data.external.error import AbstractSpakkyExternalError
에러 설명 상속
AbstractSpakkyPersistencyError 영속성 에러 기반 클래스 AbstractSpakkyFrameworkError
EntityNotFoundError 엔티티 조회 실패 AbstractSpakkyDomainError
VersionConflictError 낙관적 락 충돌 AbstractSpakkyDomainError
AbstractSpakkyExternalError 외부 서비스 에러 기반 클래스 AbstractSpakkyFrameworkError

spakky-task

태스크 시스템 관련 에러입니다.

from spakky.task.error import (
    AbstractSpakkyTaskError,
    TaskNotFoundError,
    DuplicateTaskError,
    InvalidScheduleSpecificationError,
)
에러 설명
TaskNotFoundError 레지스트리에서 태스크를 찾을 수 없음
DuplicateTaskError 중복 태스크 등록 시도
InvalidScheduleSpecificationError @scheduleinterval/at/crontab 중 하나만 필요

spakky-cache

애플리케이션 데이터 캐시 관련 에러입니다.

from spakky.cache.error import (
    AbstractSpakkyCacheError,
    CacheKeyGenerationError,
    InvalidCacheTTLError,
)
에러 설명
InvalidCacheTTLError 0 이하 TTL 값 지정 시도
CacheKeyGenerationError cache 어노테이션 key 생성 실패

spakky-tracing

분산 트레이싱 관련 에러입니다.

from spakky.tracing.error import (
    AbstractSpakkyTracingError,
    InvalidTraceparentError,
)
에러 설명
InvalidTraceparentError traceparent 헤더 형식이 유효하지 않음

플러그인 에러

spakky-fastapi

FastAPI 통합 관련 에러입니다. HTTP 상태 코드와 JSON 응답 변환을 지원합니다.

from spakky.plugins.fastapi.error import (
    AbstractSpakkyFastAPIError,
    BadRequest,
    Unauthorized,
    Forbidden,
    NotFound,
    Conflict,
    InternalServerError,
)
에러 HTTP 상태 설명
BadRequest 400 잘못된 요청
Unauthorized 401 인증 필요
Forbidden 403 접근 권한 없음
NotFound 404 리소스 없음
Conflict 409 리소스 충돌
InternalServerError 500 내부 서버 에러

JSON 응답 변환:

from spakky.plugins.fastapi.error import NotFound

try:
    user = await repository.find_by_id(user_id)
    if user is None:
        raise NotFound()
except NotFound as e:
    return e.to_response(show_traceback=False)

응답 예시:

{
    "message": "Not Found",
    "args": [],
    "traceback": null
}

spakky-sqlalchemy

SQLAlchemy 통합 관련 에러입니다.

from spakky.plugins.sqlalchemy.error import AbstractSpakkySqlAlchemyError
from spakky.plugins.sqlalchemy.orm.error import AbstractSpakkySqlAlchemyORMError
from spakky.plugins.sqlalchemy.persistency.error import AbstractSpakkySqlAlchemyPersistencyError
에러 설명 상속
AbstractSpakkySqlAlchemyError SQLAlchemy 에러 기반 클래스 AbstractSpakkyFrameworkError
AbstractSpakkySqlAlchemyORMError ORM 에러 기반 클래스 AbstractSpakkySqlAlchemyError
AbstractSpakkySqlAlchemyPersistencyError 영속성 에러 기반 클래스 AbstractSpakkySqlAlchemyError
CannotUseTableAnnotationError @Table 데코레이터 사용 오류 AbstractSpakkySqlAlchemyORMError
TargetDomainNotSpecifiedError @Table에 도메인 타입 미지정 AbstractSpakkySqlAlchemyORMError
NoSchemaFoundFromDomainError 도메인 타입에 대한 스키마 없음 AbstractSpakkySqlAlchemyORMError
CannotDetermineAggregateTypeError Aggregate 타입 추론 불가 AbstractSpakkySqlAlchemyPersistencyError
SessionNotInitializedError 세션 미초기화 상태에서 접근 AbstractSpakkySqlAlchemyPersistencyError

spakky-celery

Celery 통합 관련 에러입니다.

from spakky.plugins.celery.error import (
    AbstractSpakkyCeleryError,
    InvalidTimezoneError,
    InvalidScheduleRouteError,
)
에러 설명
AbstractSpakkyCeleryError Celery 에러 기반 클래스
InvalidTimezoneError 유효하지 않은 IANA timezone 문자열
InvalidScheduleRouteError ScheduleRoute에 유효한 스케줄 명세가 없음

spakky-security

보안 관련 에러입니다. spakky-security 에러들은 패키지별 기반 클래스 없이 AbstractSpakkyFrameworkError를 직접 상속합니다.

from spakky.plugins.security.error import (
    DecryptionFailedError,
    KeySizeError,
    PrivateKeyRequiredError,
    CannotImportAsymmetricKeyError,
    InvalidJWTFormatError,
    JWTDecodingError,
    JWTProcessingError,
    InvalidKeyConstructorCallError,
    IncompatibleKeyTypeError,
    PasswordRequiredError,
    AsymmetricKeyRequiredError,
)
에러 설명
DecryptionFailedError 복호화 실패 (키 오류 또는 데이터 손상)
KeySizeError 유효하지 않은 암호화 키 크기
PrivateKeyRequiredError 비대칭 키 연산 시 개인키 미제공
CannotImportAsymmetricKeyError 비대칭 키 임포트 실패
InvalidJWTFormatError JWT 토큰 형식 오류
JWTDecodingError JWT 토큰 디코딩 실패
JWTProcessingError JWT 토큰 처리 중 오류
InvalidKeyConstructorCallError Key() 생성자 호출 인자 오류
IncompatibleKeyTypeError 호환되지 않는 키 타입 비교
PasswordRequiredError 필수 password 파라미터 누락
AsymmetricKeyRequiredError 비대칭 키 또는 크기 파라미터 누락

spakky-outbox

Transactional Outbox 관련 에러입니다.

from spakky.outbox.error import AbstractSpakkyOutboxError
에러 설명
AbstractSpakkyOutboxError Outbox 에러 기반 클래스

spakky-opentelemetry

OpenTelemetry 통합 관련 에러입니다.

from spakky.plugins.opentelemetry.error import (
    AbstractSpakkyOpenTelemetryError,
    UnsupportedExporterTypeError,
)
에러 설명
AbstractSpakkyOpenTelemetryError OpenTelemetry 에러 기반 클래스
UnsupportedExporterTypeError 지원하지 않는 exporter 타입 설정 시 발생

spakky-logging

구조화 로깅 관련 에러입니다.

from spakky.plugins.logging.error import (
    AbstractSpakkyLoggingError,
    UnknownLogFormatError,
)
에러 설명
AbstractSpakkyLoggingError 로깅 에러 기반 클래스
UnknownLogFormatError 인식할 수 없는 로그 포맷

spakky-redis

Redis 캐시 백엔드 관련 에러입니다.

from spakky.plugins.redis.error import (
    AbstractSpakkyRedisError,
    RedisCacheOperationError,
    RedisCacheSerializationError,
)
에러 설명
AbstractSpakkyRedisError Redis 캐시 백엔드 에러 기반 클래스
RedisCacheOperationError Redis 명령 실행 또는 응답 변환 실패
RedisCacheSerializationError cache 값 직렬화 또는 역직렬화 실패

spakky-saga

사가 오케스트레이션 관련 에러입니다.

from spakky.saga.error import (
    AbstractSpakkySagaError,
    SagaFlowDefinitionError,
    SagaCompensationFailedError,
    SagaStepTimeoutError,
    SagaParallelMergeConflictError,
    SagaEngineNotConnectedError,
)
에러 설명
AbstractSpakkySagaError 사가 에러 기반 클래스
SagaFlowDefinitionError SagaFlow 정의 오류 (잘못된 흐름 구성)
SagaCompensationFailedError 보상 로직 실행 중 예외 발생
SagaStepTimeoutError step 타임아웃 초과 (엔진 내부, on_error 전략으로 라우팅)
SagaParallelMergeConflictError 병렬 단계 결과 병합 시 충돌
SagaEngineNotConnectedError 사가 엔진이 초기화되지 않은 상태에서 실행 시도

spakky-grpc

gRPC 통합 관련 에러입니다. gRPC 상태 코드 매핑 에러와 스키마 에러로 나뉩니다.

from spakky.plugins.grpc.error import (
    AbstractSpakkyGrpcError,
    AbstractGrpcStatusError,
    InvalidArgument,
    NotFound,
    AlreadyExists,
    PermissionDenied,
    Unauthenticated,
    FailedPrecondition,
    Unavailable,
    InternalError,
    UnsupportedFieldTypeError,
    MissingProtoFieldAnnotationError,
    UnsupportedResponseTypeError,
    DescriptorAlreadyRegisteredError,
)

gRPC 상태 코드 에러:

에러 gRPC 상태 코드 설명
InvalidArgument INVALID_ARGUMENT 잘못된 요청 인자
NotFound NOT_FOUND 리소스 없음
AlreadyExists ALREADY_EXISTS 리소스 이미 존재
PermissionDenied PERMISSION_DENIED 권한 없음
Unauthenticated UNAUTHENTICATED 인증 필요
FailedPrecondition FAILED_PRECONDITION 사전 조건 미충족
Unavailable UNAVAILABLE 서비스 이용 불가
InternalError INTERNAL 내부 서버 에러

스키마 에러:

에러 설명
UnsupportedFieldTypeError 지원하지 않는 protobuf 필드 타입
MissingProtoFieldAnnotationError ProtoField 어노테이션 누락
UnsupportedResponseTypeError protobuf Message나 Pydantic BaseModel이 아닌 응답
DescriptorAlreadyRegisteredError 이미 등록된 descriptor 재등록 시도

커스텀 에러 정의

도메인 에러

from spakky.domain.error import AbstractDomainValidationError

class InvalidEmailError(AbstractDomainValidationError):
    """잘못된 이메일 형식"""
    message = "Invalid email format"

class InsufficientBalanceError(AbstractDomainValidationError):
    """잔액 부족"""
    message = "Insufficient balance for this operation"

애플리케이션 에러

from spakky.core.application.error import AbstractSpakkyApplicationError

class ConfigurationError(AbstractSpakkyApplicationError):
    """설정 오류"""
    message = "Invalid configuration"

HTTP 에러 (FastAPI)

from spakky.plugins.fastapi.error import AbstractSpakkyFastAPIError
from typing import ClassVar
from fastapi import status

class TooManyRequestsError(AbstractSpakkyFastAPIError):
    """요청 제한 초과"""
    message = "Too many requests"
    status_code: ClassVar[int] = status.HTTP_429_TOO_MANY_REQUESTS

에러 처리 패턴

try-except로 특정 에러 처리

from spakky.core.pod.interfaces.container import NoSuchPodError, NoUniquePodError

try:
    service = context.get(IUserService)
except NoSuchPodError:
    logger.error("UserService not registered")
    raise
except NoUniquePodError:
    logger.error("Multiple UserService candidates")
    raise

에러 계층으로 그룹 처리

from spakky.domain.error import AbstractDomainValidationError

try:
    user = User.create(command)
except AbstractDomainValidationError as e:
    # 모든 도메인 검증 에러를 한번에 처리
    return {"error": e.message}

FastAPI 에러 핸들러

from fastapi import FastAPI
from spakky.plugins.fastapi.error import AbstractSpakkyFastAPIError

app = FastAPI()

@app.exception_handler(AbstractSpakkyFastAPIError)
async def handle_spakky_error(request, exc: AbstractSpakkyFastAPIError):
    return exc.to_response(show_traceback=app.debug)

모범 사례

1. 적절한 기반 클래스 선택

# 도메인 로직 에러 → AbstractDomainValidationError
class InvalidOrderStateError(AbstractDomainValidationError):
    message = "Cannot cancel a shipped order"

# HTTP 응답 에러 → AbstractSpakkyFastAPIError
class OrderNotFoundError(NotFound):
    message = "Order not found"

2. 상세 메시지 제공

class CircularDependencyGraphDetectedError(AbstractSpakkyPodError):
    message = "Circular dependency graph detected"

    def __init__(self, dependency_chain: list[type]) -> None:
        super().__init__()
        self.dependency_chain = dependency_chain

    def __str__(self) -> str:
        # 상세한 의존성 경로 표시
        return f"{self.message}\nPath: {' -> '.join(t.__name__ for t in self.dependency_chain)}"

3. 에러 체이닝

try:
    result = external_service.call()
except ExternalError as e:
    raise ApplicationError("External service failed") from e

4. 로깅과 함께 사용

import logging

logger = logging.getLogger(__name__)

try:
    pod = context.get(IService)
except NoSuchPodError as e:
    logger.error(f"Service not found: {e}", exc_info=True)
    raise

에러 테스트

import pytest
from spakky.domain.error import AbstractDomainValidationError

class InvalidEmailError(AbstractDomainValidationError):
    message = "Invalid email"

def test_invalid_email_error():
    with pytest.raises(InvalidEmailError) as exc_info:
        raise InvalidEmailError()

    assert exc_info.value.message == "Invalid email"
    assert isinstance(exc_info.value, AbstractDomainValidationError)

def test_error_hierarchy():
    error = InvalidEmailError()

    # 계층 확인
    assert isinstance(error, AbstractDomainValidationError)
    assert isinstance(error, Exception)