레거시 현대화 · Legacy Modernization

같은 DTO에서 API마다 다른 필수값을 검증하기

다운로드 DTO 하나를 여러 API에서 재사용하면서, 각 API에 필요한 필드만 검사하도록 Bean Validation 그룹과 AOP를 조합했습니다.

Spring MVCBean ValidationSpring AOPLegacy Modernization

같은 DTO라도 API마다 필요한 값이 달랐습니다

개선 전에는 메서드마다 필수값과 파일 경로를 따로 검사했습니다. 같은 요청 DTO를 여러 다운로드·조회 기능에서 공유했지만, 실제로 필요한 필드는 기능마다 달랐습니다. DTO 전체에 한꺼번에 필수 제약을 걸면 해당 API가 쓰지 않는 필드 때문에 정상 요청까지 막히는 문제가 생깁니다.

아래 코드는 실제 동작이 드러나도록 클래스·테이블·설정 이름을 바꿔 줄인 예시입니다.

if (request.getOrderKey() == null || request.getItemSequence() == null) {
    throw new RequestValidationException("invalid request");
}

if (request.getFileName() == null || request.getFileName().contains("..")) {
    throw new RequestValidationException("invalid path");
}

검증을 세 단계로 나눴습니다

검증을 세 단계로 나눴습니다 데이터 표
검증 계층예시필요한 정보
구조·필수값@NotBlank, @NotNull, Validation 그룹요청 DTO
보안·경로상위 경로 이동 문자열, 허용 경로 확인요청 문자열과 경로 규칙
업무 정합성주문과 요청 사용자의 소유 관계DB 주문 정보와 법인 조건

값의 형태와 필수 여부는 Bean Validation에 맡겼습니다. 파일 경로가 안전한지는 별도의 보안 규칙이고, 주문 소유 관계는 DB를 조회해야 알 수 있는 업무 규칙입니다. 셋을 하나의 Validator에 몰아넣지 않으니 실패 이유와 재사용 범위도 자연스럽게 나뉘었습니다.

DTO에는 필드의 검증 의미만 선언했습니다

아래 예시는 실제 커스텀 애너테이션, DTO, 메시지 코드와 그룹 이름을 가명으로 바꾼 코드입니다.

public class DownloadRequest {

    @NotBlank(groups = USER_KEY.class)
    private String userKey;

    @NotBlank(groups = ORDER_KEY.class)
    private String orderKey;

    @NotNull(groups = ITEM_SEQUENCE.class)
    private Integer itemSequence;

    @NotBlank(groups = FILE_NAME.class)
    private String fileName;
}

DTO는 필드가 어떤 검증 의미를 갖는지만 선언합니다. 어느 API가 그 필드를 필수로 사용하는지는 호출 메서드가 결정합니다.

호출 메서드가 필요한 그룹을 조합했습니다

동일한 DTO를 받더라도 다운로드 종류에 따라 그룹 조합을 다르게 선언했습니다. 여러 호출이 같은 DTO를 공유해도 각 API에 필요한 검증 그룹만 선택할 수 있게 했습니다. 실제 애너테이션과 메서드명은 일반적인 이름으로 바꿨습니다.

@ValidateRequest(
    type = DownloadRequest.class,
    groups = { ORDER_KEY.class, ITEM_SEQUENCE.class, FILE_NAME.class }
)
public FileResult downloadOne(DownloadRequest request) {
    return fileService.downloadOne(request);
}

@ValidateRequest(
    type = DownloadRequest.class,
    groups = { ORDER_KEY.class, FILE_NAME.class }
)
public ViewResult openPreview(DownloadRequest request) {
    return fileService.openPreview(request);
}

AOP는 메서드 실행 전에 선언된 DTO와 그룹을 찾아 표준 Validator에 전달합니다. 첫 번째 제약 위반이 발견되면 원래 메서드를 실행하지 않고 공통 예외 처리기로 전달합니다.

for (Class<?> group : activeGroups) {
    Set<ConstraintViolation<Object>> violations =
        validator.validate(target, group);

    if (!violations.isEmpty()) {
        throw new RequestValidationException("validation failed");
    }
}

메시지와 오류 화면 처리

검증 메시지는 기존 다국어 메시지 체계와 연결하되 실제 메시지 코드, 빈 이름과 오류 처리 클래스명은 공개하지 않습니다. 검증 로직은 실패 정보만 전달하고, 사용자에게 어떤 화면과 문구를 보여줄지는 공통 예외 처리기가 맡습니다.

일부 요청은 검증 실패 후 브라우저의 이전 화면으로 돌아가야 했습니다. 이 경우 오류 응답에 후속 화면 동작 정보를 함께 전달했습니다. 실제 페이로드 타입과 애너테이션 이름은 공개하지 않습니다.

경로 보안과 소유 관계는 따로 검증했습니다

필수값 검증을 통과한 뒤에는 파일 경로 규칙과 주문 소유 관계를 별도로 확인했습니다.

pathPolicy.validate(request.getFileName());
ownershipPolicy.validateOwnership(
    request.getUserKey(),
    request.getOrderKey()
);

validateOwnership()은 요청 값의 형식이 아닌 실제 업무상 접근 가능 여부를 확인합니다. DTO 검증을 통과해도 업무 권한이 보장되는 것은 아니기 때문입니다.

검증은 이렇게 진행했습니다

기능 테스트에서는 같은 DTO를 쓰는 메서드마다 올바른 Group이 선택되는지 확인했습니다. 선택 필드 처리, 필수값 실패 시 메서드 본문이 실행되지 않는지, 다국어 메시지와 후속 경로·소유 관계 검증까지 이어서 점검했습니다.

20개 시나리오·557개 케이스는 이 검증 설계만 따로 떼어 낸 수치가 아니라 다운로드 시스템 전체 기능 테스트의 결과입니다. 처음에는 548건이 성공하고 9건이 실패했지만, 결함을 고친 뒤에는 557건이 모두 통과했습니다. 본사·유럽법인 통합 테스트도 별도로 진행했습니다.

한계와 운영상 주의점

  • 검증 애너테이션이 누락된 메서드는 이 AOP 검증을 받지 않으므로 코드 리뷰 기준이 필요합니다.
  • 상속형 DTO를 도입하면 타입 탐색 규칙을 다시 검토해야 합니다.
  • 한 그룹 안에 여러 위반이 있으면 표시 순서에 의존하지 않아야 합니다.
  • 경로 보안과 업무 정합성 검증의 호출 누락 여부도 별도로 점검해야 합니다.
  • 프록시를 거치지 않는 내부 호출에는 같은 방식이 자동 적용된다고 가정하면 안 됩니다.

정리

표준 Bean Validation은 그대로 두고, 반복되던 “이 메서드가 DTO의 어느 항목을 요구하는가”만 애너테이션으로 선언했습니다. 경로 보안과 주문 소유 관계는 별도로 검사해 필수값 검증과 접근 가능 여부를 혼동하지 않게 했습니다.

TECHNICAL SERIES

DNA2 레거시 현대화

DB 연결 생명주기부터 기존 요청 형식, 다국어 처리, 입력 검증과 런타임 충돌까지 이어지는 현대화 기록입니다.

시리즈 전체 보기

현재 5/6

  1. 1요청마다 Connection Pool과 SessionFactory를 만들면 생기는 문제
  2. 2DB Link 제거 후 복수 DataSource를 한 트랜잭션으로 관리하기
  3. 3레거시 URL의 언어 규칙을 Spring Locale로 연결하기
  4. 4축약 파라미터는 유지하고 DTO 필드명은 명확하게 바꾸기
  5. 5같은 DTO에서 API마다 다른 필수값을 검증하기
  6. 6JVM 클래스 로딩 로그로 추적한 Oracle JDBC 드라이버 충돌