포스트

Common

JSP 기반 시스템의 구조적 문제를 해결한 아키텍처 전환기: DTO 중심 아키텍처 전환과 검증 체계 개선

레거시 JSP 시스템의 구조적 한계 극복기 - DTO 중심 아키텍처 전환과 검증 체계 개선

들어가며

오랜 기간 유지되어 온 JSP 기반의 웹 시스템은 빠르게 변화하는 비즈니스 요구사항과 정제되지 않은 데이터 흐름, 그리고 복잡하게 얽힌 UI 중심 로직으로 인해 유지보수가 점점 어려워지고 있었습니다. 특히 Map 기반의 비정형 데이터 처리, 검증 및 예외 처리의 부재, 감사 추적의 어려움이 시스템 신뢰성을 떨어뜨리고 있었습니다.

이 글은 이 문제들을 Swagger 기반의 API 명세 정립, DTO 중심의 정적 타입 구조 도입, 유효성 검증 체계 고도화, 도메인 중심 설계 재구성, 감사 로깅 체계 도입으로 어떻게 풀었는지 기록합니다. 문제 네 가지와 해결 전략 다섯 가지는 대체로 순서대로 대응합니다.


문제 정의: 무엇이 문제였는가?

1. UI 중심의 비즈니스 로직

  • JSP 내에 로직이 과도하게 집중되어 있어, 프론트와 백엔드의 역할 구분이 모호했습니다.
  • 화면을 구성하기 위해 JSP에서 직접 쿼리를 호출하고 조건 분기를 수행하는 등, 프레젠테이션 계층(화면을 그리는 계층)이 서비스 로직을 침범하고 있었습니다.

2. Map 기반의 데이터 처리

  • 컨트롤러 및 서비스 계층에서 Map<String, Object> 형태로 데이터를 주고받아 타입 안정성이 떨어졌습니다.
  • 키 이름과 값의 타입이 코드에 드러나지 않으므로 정적 분석이 어렵고, 자동 완성과 컴파일 타임 검증도 불가능했습니다. 그 결과 키 오타나 타입 불일치는 실행 중에야 드러났습니다.

3. 검증 및 예외 처리 체계 부재

  • 사용자 입력 값에 대한 명확한 검증 체계가 존재하지 않아 런타임 오류가 빈번하게 발생했습니다.
  • 에러가 발생해도 일관된 예외 처리 및 사용자 친화적인 응답 구조가 없어 오류를 추적하기 어려웠습니다.

4. 변경 이력 및 감사 추적의 부재

  • 사용자 요청으로 인해 어떤 데이터가 언제, 어떻게 변경되었는지를 추적할 수 있는 로깅이나 히스토리 기능이 부재했습니다.

해결 전략

1. Swagger 기반 API 명세 수립

첫 번째 문제(UI 중심 로직)를 풀려면 화면과 서버 사이의 경계부터 정해야 했습니다.

  • OpenAPI 3.0 기반의 Swagger 문서 작성을 통해 프론트-백 간 계약을 명확히 했습니다.
  • Springdoc을 활용하여 컨트롤러에 대한 명세를 자동화하고, API 문서 유지보수 비용을 줄였습니다.

명세가 먼저 있으면 요청과 응답의 형태를 두고 개발자끼리 확인하는 일이 줄고, 그 명세를 테스트 자동화의 기준으로 쓸 수 있습니다.

2. DTO 중심의 타입 안정성 확보

명세로 경계를 정한 뒤에는 그 경계를 지나는 데이터를 Map 대신 타입으로 표현했습니다.

  • 각 API 요청/응답을 위한 Request/Response DTO(Data Transfer Object, 계층 간 전달용 객체) 클래스를 도입해, 명확한 데이터 구조를 정의했습니다.
  • 도메인 모델과 API DTO 간 분리를 통해 계층 간 의존성을 제거하고, API 단 변경이 도메인 로직에 영향을 미치지 않도록 설계했습니다.
1
public record UserRegistrationRequest(String username, String email, String password) {}
  • Map → DTO 구조로 전환함으로써 IDE 자동완성, 컴파일 타임 검증, 정적 분석이 가능해졌습니다.

3. Jakarta Bean Validation + 커스텀 유효성 검증

DTO로 필드가 드러나자, 세 번째 문제였던 입력 검증을 필드 단위 애너테이션으로 선언할 수 있게 됐습니다.

  • Jakarta Bean Validation 3.0을 활용해 기본적인 필수값 및 포맷 검증을 수행했습니다.
  • 복합 조건 검증 (ex. 특정 필드 조합의 유효성 등)을 위해 커스텀 ConstraintValidator를 구현하여 다단계 유효성 검증 체계를 도입했습니다.
1
2
3
4
5
6
7
8
9
10
11
@ValidPassword
public class UserRegistrationRequest {
    @NotBlank
    private String username;

    @Email
    private String email;

    @PasswordComplexity
    private String password;
}
  • Mapstruct를 통해 매핑을 하며, 매핑 전후로 직접 작성한 Validator를 통한 검증을 수행하도록 했습니다.
  • 유효성 실패 시 에러 응답을 일관되게 처리하도록 @ControllerAdvice와 @ExceptionHandler 기반의 전역 예외 처리 시스템을 구성했습니다.

4. 도메인 중심 서비스 계층 재구성

  • 기존 JSP에 흩어진 비즈니스 로직을 도메인 기반의 Service 계층으로 이전하고, 역할과 책임을 명확히 분리했습니다.
  • Input → Validation → Domain Logic → Persistence 흐름을 표준화하고, 로직 복잡도에 따라 Command / Query 책임도 분리했습니다.

5. 변경 이력 로깅 및 감사 가능성 확보

  • 주요 도메인 데이터에 대해 변경 이력을 기록하는 AuditLogger를 구성하여 사용자 행위를 추적 가능하게 만들었습니다.
  • 예를 들어, 계약 상태 변경, 승인 처리 등의 행위 로그를 JSON 구조로 Elasticsearch 및 DB에 저장하여 Kibana를 통해 검색 가능하도록 구성했습니다. 이를 통해 ISMS, ITGC 등 각종 감사에 대응하기 수월해졌습니다. 로그 수집 체계 자체는 ISMS 대응을 위한 로그 수집 체계 개선에 따로 정리했습니다.
1
2
3
4
5
6
7
{
  "action": "contract_status_updated",
  "user": "admin01",
  "before": "PENDING",
  "after": "APPROVED",
  "timestamp": "2025-05-22T10:15:30+09:00"
}

개선 효과

항목개선 전개선 후
타입 안정성Map 기반, 런타임 오류 빈번DTO 기반, 컴파일 타임 검증
검증 체계화면 단 JavaScript 또는 누락다단계 유효성 검증 + 공통 예외 처리
감사 추적변경 이력 없음모든 주요 이벤트 로깅
유지보수성화면 코드 복잡도 심각계층 분리로 로직 집중도 개선
협업 생산성API 명세 누락, 커뮤니케이션 과잉Swagger 기반 계약 명확화

마무리하며

이번 작업은 기술 스택을 바꾸는 일보다 로직의 자리를 다시 정하는 일에 가까웠습니다. JSP에 있던 로직을 서비스 계층으로 옮기고, 경계를 지나는 데이터를 DTO로 고정하고, 그 위에 검증과 감사 로그를 얹었습니다. 검증 가능성, 안정성, 감사 추적 가능성을 기준으로 설계를 정비한 덕분에 기능 추가와 유지보수의 부담이 줄었습니다.

리팩토링은 집을 정리하는 일과 닮아 있습니다. 매일 조금씩 정리하면 쾌적한 상태를 유지할 수 있지만, 미루면 손대기 어려울 만큼 복잡해집니다. 물건의 자리가 정해진 집이 치우기 쉽듯, 구조와 책임이 정의된 코드는 다음 리팩토링도 수월합니다. 그래서 큰 전환 한 번보다 작은 개선을 자주 반복하는 편이 낫다고 생각합니다.

참고

아키텍처와 마이그레이션
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

변경이력

2번 수정

  1. docs(notes): cite sources and ease reading in legacy-jsp-system-refactoring-1
  2. docs(posts): normalize common note metadata and headings

댓글

아직 댓글이 없습니다