모든 글

모듈 경계에서 예외를 변환하고 전파하는 법

구현 예외를 소유 모듈 안에 가두고, 상위 계층으로 의존성이 새지 않도록 변환 경계를 설계합니다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

외부 API 실패 하나를 코드 위로 따라가 보자. 클라이언트 모듈 안에서 HTTP 라이브러리가 자기 예외를 던진다. 그 타입이 코어 모듈을 지나 컨트롤러 어드바이스까지 올라가면 내부 구현 선택이 모든 상위 계층의 의존성이 된다. 예외 변환은 그 누출을 어디에서 멈출지 정하는 일이다.

실패가 두 모듈 경계를 지나는 과정을 따라간다

클라이언트 모듈은 라이브러리 예외를 잡아 자기 모듈의 예외로 감싼다. 사용하는 쪽은 어떤 HTTP 구현이 실패했는지 몰라도 클라이언트 호출 실패를 다룰 수 있다. 모듈은 자기 실패 계약을 품고 있으므로 옮기거나 내부 라이브러리를 바꿔도 온전하게 남는다. 모든 예외를 공통 모듈에 모으면 독립적인 모듈마다 그 공통 의존성을 다시 가져야 한다.

클라이언트를 사용하는 구현 또는 도구 레이어에서는 이 예외를 더 위로 허용할지 결정한다. 클라이언트 예외를 잡아 코어 예외로 바꾼 뒤 상위로 전달할 수 있다. 이 지점이 격벽이다. 바깥 모듈의 예외는 원래 타입으로 넘어오지 못하고, 비즈니스 흐름은 HTTP 라이브러리가 아니라 애플리케이션의 언어를 다룬다.

클라이언트 예외를 전역 컨트롤러 어드바이스까지 올리는 방식이 무조건 틀린 것은 아니다. 클라이언트 하나를 쓰는 작은 애플리케이션이라면 충분히 직관적일 수 있다. 클라이언트가 늘면 프레젠테이션 계층이 아래 모듈의 예외를 모두 아는 목록으로 변한다. 그 의존성의 확산이 비싸지기 전에 어디에 벽을 세울지가 선택의 핵심이다.

예외를 던질 계층은 실패의 의미에 따라 달라진다

모듈 사이에서 예외를 변환한다고 모든 예외가 한 계층에서 시작해야 하는 것은 아니다. 잘못된 HTTP 입력은 프레젠테이션 경계에서 거절해 온전한 값을 안쪽으로 넘긴다. 포인트 부족이나 사용자 부재는 그 조건을 발견한 비즈니스 행위에서 자연스럽게 드러날 수 있다.

저장소와 클라이언트를 조율하는 구현 레이어가 있다면 많은 실패를 그곳에서 끝낼 수 있다. 더 작은 애플리케이션에는 그런 레이어가 없을 수 있으므로 비즈니스 서비스에서 직접 던지는 편이 분명하다. 의존성 역전으로 더 강하게 격리할 수도 있지만 소프트웨어 크기에 비해 구조가 과해질 수 있다.

각 모듈이 어떤 예외를 노출하고, 사용하는 쪽이 그 예외를 어디까지 허용할지 정한다. 단순한 catch와 변환만으로 충분한 경우도 있다. 모듈을 온전하게 지키되 “서비스는 예외를 던지지 않는다” 같은 계층 취향을 보편 규칙으로 만들 필요는 없다.