TIL - 공통 모듈의 ErrorCode, 어떻게 관리해야 할까

2026. 4. 26. 02:05·내배캠

공통모듈에서 ErrorCode 관리하기

모노레포 멀티모듈 환경에서 ErrorCode를 공통 모듈에 관리하다 보니 한계가 보였다.
ErrorCode를 관리하는 두 가지 방법을 정리해 보았다.

방법 1: 하나의 enum에 모든 ErrorCode를 담기

이 방법은 가장 일반적이고 직관적인 방식이다.

공통 모듈에 ErrorCode enum 하나를 두고

모든 도메인의 에러를 여기에 추가한다.

package common.exception;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

    // ── Auth / Token ──────────────────────────────────
    UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "로그인이 필요합니다."),

    // ── User ──────────────────────────────────────────
    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_NOT_FOUND", "존재하지 않는 사용자입니다."),

    // ── Competition ──────────────────────────────────
    COMPETITION_NOT_FOUND(HttpStatus.NOT_FOUND, "COMPETITION_NOT_FOUND", "존재하지 않는 대회입니다."),

    // ── Common ────────────────────────────────────────
    INVALID_INPUT(HttpStatus.BAD_REQUEST, "INVALID_INPUT", "입력값이 유효하지 않습니다."),
    ;

    private final HttpStatus status;
    private final String code;
    private final String message;
}

사용할 때는 BusinessException에 원하는 ErrorCode를 넘겨주면 된다.

if (user.isEmpty()) {
    throw new BusinessException(ErrorCode.USER_NOT_FOUND);
}

if (competition.isEmpty()) {
    throw new BusinessException(ErrorCode.COMPETITION_NOT_FOUND);
}

한 곳만 보면 모든 에러가 보이니까, 처음에는 편하다.
하지만 도메인이 늘어날수록 문제가 드러난다.

문제점

1. 파일이 비대해진다

도메인이 늘어날 때마다 ErrorCode enum이 끝없이 길어진다.
User, Competition, Trade, Notification, Ranking…
도메인마다 5~10개씩만 추가해도 금방 수백 줄짜리 파일이 된다.

 

2. 브랜치 충돌이 잦다

여러 명이 각자 다른 도메인을 작업하는데, 결국 같은 파일을 수정하게 된다.
A는 Competition 에러를 추가하고, B는 Trade 에러를 추가한다.
머지 시점에 충돌이 난다.

도메인이 서로 독립적인데도 ErrorCode 때문에 작업이 묶여버린다.

 

3. 도메인 경계가 무너진다

가장 본질적인 문제인데, 모노레포 멀티모듈 환경에서는 각 서비스가 독립된 모듈로 분리되어 있다.
competition-service, asset-service처럼 말이다.

 

그런데 모든 모듈이 공통 모듈의 거대한 ErrorCode 하나를 의존하면 어떻게 될까?

competition-service가 자기와 무관한 USER_NOT_FOUND, TRADE_FAILED까지 모두 알게 된다.

도메인을 모듈로 분리한 의미가 흐려진다.

방법 2: ErrorCode를 인터페이스로 두고, 각 도메인에서 구현하기

핵심은 ErrorCode를 enum이 아니라 인터페이스로 만드는 것이다.

package common.exception;

public interface ErrorCode {
    HttpStatus getStatus();
    String getCode();
    String getMessage();
}

공통 에러는 공통 모듈에 CommonErrorCode로 둔다.

package common.exception;

@Getter
@RequiredArgsConstructor
public enum CommonErrorCode implements ErrorCode {
    INVALID_INPUT(HttpStatus.BAD_REQUEST, "INVALID_INPUT", "입력값이 유효하지 않습니다."),
    UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "로그인이 필요합니다."),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "서버 오류가 발생했습니다."),
    ;

    private final HttpStatus status;
    private final String code;
    private final String message;
}

각 도메인 모듈은 자기만의 ErrorCode enum을 갖는다.

// competition-service 모듈
@Getter
@RequiredArgsConstructor
public enum CompetitionErrorCode implements ErrorCode {
    COMPETITION_NOT_FOUND(HttpStatus.NOT_FOUND, "COMPETITION_NOT_FOUND", "존재하지 않는 대회입니다."),
    ;

    private final HttpStatus status;
    private final String code;
    private final String message;
}

@Getter로 생성된 메서드 시그니처가 인터페이스와 일치하므로,
자동으로 인터페이스 구현을 만족하게 된다.

 

BusinessException은 수정할 필요가 없다.
어차피 ErrorCode 타입을 받으니까.

package common.exception;

@Getter
public class BusinessException extends RuntimeException {

    private final ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
}

사용하는 쪽도 자연스럽다.

// competition-service에서
throw new BusinessException(CompetitionErrorCode.COMPETITION_NOT_FOUND);

// user-service에서
throw new BusinessException(UserErrorCode.USER_NOT_FOUND);

무엇이 해결되었나

1. 파일이 작아진다

각 도메인의 ErrorCode가 자기 모듈 안에 있다.
파일 하나가 비대해질 일이 없다.

 

2. 브랜치 충돌이 사라진다

A는 CompetitionErrorCode를, B는 TradeErrorCode를 수정한다.
다른 파일이니 충돌도 없다.

 

3. 도메인 경계가 명확해진다

competition-service는 CompetitionErrorCode와 CommonErrorCode만 안다.
자기와 무관한 에러는 보이지도 않는다.

마무리

enum은 상속이 안 되지만, 인터페이스는 구현할 수 있다. 이 단순한 사실 하나로 enum의 다형성을 살릴 수 있었다.

모노레포 멀티모듈에서 공통 모듈은 모든 모듈이 공유하는 최소한의 것만 두는 게 좋다.

 

ErrorCode도 마찬가지다.
"인터페이스(약속)는 공통 모듈에, 구현(실제 에러 목록)은 각 도메인에"
이 원칙으로 정리하면 깔끔해진다.

'내배캠' 카테고리의 다른 글

TIL - Redis 및 Kafka 사용 시 DB와의 분산트랜잭션 문제 해결하기  (0) 2026.05.02
전자기기로 이해하는 헥사고날 아키텍처  (1) 2026.04.30
TIL - 생성자에 private과 protected, 언제 어떻게 쓸까  (0) 2026.04.24
TIL - 실시간 랭킹 구현 방법  (0) 2026.04.23
TIL - 개발 키워드 정리  (0) 2026.04.15
'내배캠' 카테고리의 다른 글
  • TIL - Redis 및 Kafka 사용 시 DB와의 분산트랜잭션 문제 해결하기
  • 전자기기로 이해하는 헥사고날 아키텍처
  • TIL - 생성자에 private과 protected, 언제 어떻게 쓸까
  • TIL - 실시간 랭킹 구현 방법
MvA
MvA
백엔드 개발자 김재현입니다. 주로 공부하면서 느낀점을 기록합니다.
  • MvA
    Man vs Ai
    MvA
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • Java (6)
      • Python (8)
        • 딥러닝 (1)
        • 머신러닝 (7)
      • JavaScript (2)
      • 내배캠 (60)
      • 개인 프로젝트 (11)
      • 책 후기 (5)
      • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    TiL
    배포
    머신러닝
    내일배움캠프
    딥러닝
    Riot API
    아키텍처
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
MvA
TIL - 공통 모듈의 ErrorCode, 어떻게 관리해야 할까
상단으로

티스토리툴바