개발 slecs

자금흐름 투명성 증빙 PDF 기능 추가

목차

자금흐름 관련 데이터를 PDF로 뽑아주는 기능을 추가했다. 원래 화면에서 데이터를 조회하는 기능은 이미 있었는데, 회계 감사나 내부 결재 과정에서 스크린샷이 아닌 공식 증빙 형태의 문서가 필요하다는 요구가 있었다. 단순히 화면을 PDF로 변환하는 수준이 아니라, 서버에서 정해진 양식에 맞게 데이터를 담아 파일을 내려주는 방식이다.

규모가 크지 않은 작업처럼 보이지만, 자금 데이터가 담기는 순간부터는 정확성 요건이 다르게 적용된다. 화면에서 잘못된 값이 나왔을 때는 "다시 조회해보세요"로 끝나지만, PDF로 나간 증빙 문서는 회수가 어렵다. 그래서 파라미터 검증부터 쿼리 설계, 트랜잭션 범위까지 평소보다 꼼꼼하게 잡았다.

파라미터 쪽에서는 조회 기간, 계정 식별자, 출력 유형이 제대로 들어오는지 Controller 진입 단계에서 먼저 막았다. null 체크, 날짜 포맷 검증, 기간 범위 초과 여부. 특히 기간 파라미터는 선택값으로 열어두면 MyBatis XML의 <if test> 분기에서 전체 조회로 빠지는 케이스가 생긴다. 자금 데이터 전체를 PDF로 뽑는 건 용량 문제도 있고, 그 자체로 보안 이슈가 될 수 있어서 기간 조건은 필수 입력으로 설계했다.

내부 클래스 구조와 SQL 매핑

Spring MVC + MyBatis 조합에서 이번 작업은 RequestVO와 ResponseVO를 내부 클래스로 분리해서 잡는 패턴을 따랐다. Controller에서 요청 파라미터를 VO에 바인딩하고, Service가 Mapper를 호출해서 결과 리스트를 받아 PDF 생성 로직으로 넘기는 흐름이다.

// 파라미터 바인딩 - 기간 조건 필수
public class FundFlowCertPdfRequest {
    @NotBlank
    private String accountId;

    @NotNull
    private LocalDate fromDate;

    @NotNull
    private LocalDate toDate;

    private String certType = "FULL"; // FULL / SUMMARY, 기본값 FULL
}

SQL은 XML에서 기간 조건을 <if> 없이 고정으로 넣었다. 처음에 <if test="fromDate != null"> 형태로 작성했다가 파라미터 누락 시 전체 조회가 나가는 걸 테스트에서 발견하고 바꿨다. 필수 파라미터는 XML이 아니라 Controller나 Service 레이어에서 막는 게 맞다.

<select id="selectFundFlowForCert" resultType="FundFlowRow">
    SELECT
        acct_id,
        txn_dt,
        amount,
        txn_type,
        remark
    FROM fund_txn
    WHERE acct_id = #{accountId}
      AND txn_dt BETWEEN #{fromDate} AND #{toDate}
    ORDER BY txn_dt ASC
</select>

결과 매핑은 resultMap을 명시적으로 정의했다. 카멜케이스 자동 변환 설정이 안 된 환경이라 txn_dt -> txnDt, acct_id -> acctId 같은 매핑을 일일이 잡아야 했다. 처음에 자동 매핑만 믿고 갔다가 amount 컬럼이 null로 내려오는 걸 로컬에서 발견했다. 숫자 필드가 null로 PDF에 찍히면 증빙 문서로서 의미가 없으니, 매핑 누락은 테스트 단계에서 다 잡아두는 게 맞다.

트랜잭션 분리와 예외 처리 판단

가장 판단이 필요했던 부분은 트랜잭션 범위였다. PDF 생성 자체는 읽기 작업이지만, 발급 이력을 별도 테이블에 INSERT하는 요건이 같이 있었다. 조회와 이력 저장을 같은 트랜잭션으로 묶으면 readOnly 설정과 충돌이 나고, 파일 생성이 실패했을 때 이력이 잘못 찍히는 케이스도 생긴다.

구간 트랜잭션 설정 이유
자금 데이터 조회 readOnly = true 불필요한 락 방지
발급 이력 INSERT 기본 트랜잭션 쓰기 허용 필요
PDF 바이트 스트림 생성 트랜잭션 외부 파일 I/O는 DB 트랜잭션과 무관

순서는 "조회 성공 - 파일 생성 성공 - 이력 INSERT"로 잡았다. 이력을 먼저 찍고 파일 생성이 실패하면 발급된 것처럼 기록이 남아버린다. 자금 증빙 기능에서 그런 불일치가 생기면 감사 시점에 해명하기 까다로워진다.

예외 처리는 RuntimeException을 Service 밖으로 전파하지 않고 Controller에서 잡아서 에러 응답으로 변환하는 방식을 택했다. PDF 생성이 실패했을 때 500으로 터지는 것보다 사유를 담은 응답이 내려가는 게 운영 대응에 훨씬 낫다. "파일 생성 실패", "데이터 없음", "파라미터 오류" 케이스를 구분해서 응답 코드와 메시지를 잡아뒀다.

로그는 중요 입출력값을 INFO로 남겼다. 입력 accountId, 기간, certType, 발급 이력 KEY 값. DEBUG로 남기면 운영 환경에서 안 나오니까, "특정 날짜에 발급한 PDF가 잘못됐다"는 민원이 들어왔을 때 추적 가능한 값들은 INFO로 고정해야 한다.

배포는 로컬 기동 확인 - 개발 DB 반영 - 서버 배포 순서로 진행했다. 무중단 배포라 신규 버전이 완전히 뜬 걸 헬스체크 엔드포인트로 확인하고 나서 프록시를 전환했다. 헬스체크가 없는 상태에서 트래픽을 붙이면 기동 중인 인스턴스에 PDF 요청이 들어가고, 그 에러가 사용자에게 그대로 노출된다. 롤백을 위한 이전 버전 JAR도 배포 전에 보관해뒀다.

돌아보면 이런 "작은 기능 추가"가 체크포인트가 생각보다 많다. 파라미터 검증, SQL 매핑 확인, 트랜잭션 분리, 예외 처리 분기, 로그 레벨, 배포 순서까지. 기능의 코드 복잡도가 낮아도 데이터 성격에 따라 신경 써야 하는 항목 수는 달라진다. 자금 데이터처럼 "잘못 나오면 안 되는" 영역일수록 코드 자체보다 그 주변 설계가 더 중요하다는 걸 이번에 다시 확인했다.

댓글 0

첫 댓글 달아줘.