주식회사 더존테크윌
세무사·회계법인 대상 SaaS를 제공하는 회사. 기존 레거시 세무 정보 서비스를 신규 SaaS 플랫폼으로 재구축하고 있고, 그중 백엔드·플랫폼 영역을 담당하고 있습니다.
신규 SaaS 플랫폼 백엔드
프로젝트 초기 단계부터 참여해, 레거시 PHP 기반 세무 정보 서비스를 Spring Boot 3.3과 Spring Cloud 기반 마이크로서비스로 재구축하고 있습니다. 현재 도메인·플랫폼 책임 단위로 분리된 다수의 마이크로서비스가 구축되어 상용 오픈을 준비 중인 단계입니다. API Gateway, 사내 공통 라이브러리, Kubernetes GitOps 파이프라인, 관측성·로깅처럼 여러 서비스가 함께 쓰는 기반을 다루면서, 세법 도메인 API도 같이 담당하고 있습니다. 도메인 콘텐츠는 법령·판례·예규와 그에 연결된 세무자료로 구성되어 시점 기준 조회와 변경 이력 추적이 핵심 요구사항이고, 레거시 Oracle DB는 다년간 누적된 테이블이 많아 도메인 진입 비용 자체가 컸습니다.
프로젝트 합류 시점에 공통 플랫폼 영역과 도메인 API를 동시에 맡게 됐습니다. 사내 공통 Spring Boot Starter, API Gateway 인증/인가 책임 경계 재설계, Kubernetes GitOps 파이프라인, 관측성·로깅 등 여러 서비스가 함께 쓰는 기반을 설계·구현했고, 세법 영역의 도메인 API도 함께 개발했습니다.
- 도메인·플랫폼 책임 단위로 분리한 다수의 마이크로서비스 (Spring Boot 3.3)
- 상용 오픈 준비 중인 SaaS 플랫폼
- 기존 서비스의 운영 고객 전체 전환을 고려한 구조 설계
- 도메인 콘텐츠: 법령·판례·예규 + 연결된 세무자료 (시점 기준 조회·변경 이력 추적 필요)
- 레거시 Oracle DB: 다년간 누적된 다수의 테이블 (DB 동결 + API 재설계 전략 채택)
- Backend
- Java 21, Spring Boot 3.3, Spring Cloud Gateway, JPA, QueryDSL, OpenFeign, Resilience4j
- Database
- Oracle (legacy), PostgreSQL (pg_trgm), Caffeine
- Infra
- Kubernetes, Jenkins, ArgoCD, Jib, Kustomize, Nexus, Harbor
- Observability
- Elasticsearch, Filebeat, Elastic APM, log4j2 JSON
- Identity / Auth
- JWT (RS256), OAuth2, LDAP
레거시 Oracle 스키마 분석 · 도메인 문서화
- 레거시 Oracle DB에 다년간 누적된 테이블이 많고, 컬럼 의미와 관계에 대한 사내 문서가 정리돼 있지 않은 상태였습니다.
- 신규 API 코드 작성에 들어가기 전에, 핵심 도메인 영역(법령·판례·예규·세무자료 등)의 테이블 구조와 실제 데이터 패턴을 분석해 사내 문서로 정리했습니다. 코드만 빨리 짜고 넘어가지 않고, 다른 팀원이 도메인을 따라 들어올 수 있는 진입점을 만들고 싶었습니다.
- 이후 합류하는 백엔드·기획·세무사가 도메인을 파악할 때 같은 문서를 참고하게 됐고, "이 컬럼이 뭐였더라"를 사람에게 매번 물어보지 않아도 되는 상태가 됐습니다.
사내 공통 Spring Boot Starter 설계·구현
- 신규 마이크로서비스를 만들 때마다 응답 포맷, 예외 처리, BaseEntity, 권한 enum 등을 반복 구현하고 있었고, 사람마다 패턴이 조금씩 달라지면서 같은 종류의 버그가 여러 서비스에서 반복되는 상태였습니다.
- 공통 기능을 사내 Spring Boot Starter 형태로 제공하기로 했습니다. core / data / web / starter 4개 모듈로 나눠 Auto-Configuration 기반으로 의존성 한 줄만 추가하면 표준 구성이 따라오게 했고, 사내 Nexus에 SemVer 기반으로 자동 배포되도록 정리했습니다.
- 권한 enum을 core 모듈에 한 번 정의해, 권한 추가나 변경이 라이브러리 한 곳에서만 일어나도록 정리했습니다.
- 신규 서비스의 보일러플레이트가 크게 줄었고, 권한·응답·예외처럼 서비스마다 달라지면 문제가 생기는 기준들이 한 곳에서 관리되는 상태가 됐습니다.
점진적 MSA 이관 — 레거시 DB 유지 + API 재설계
- 레거시 PHP 모놀리스의 Oracle DB는 수년 치 운영 데이터가 명확한 PK, 외래키, 코멘트 없이 운영되어서 DB까지 한 번에 재설계하면 마이그레이션 리스크와 운영 중단 시간이 너무 컸습니다.
- DB는 그대로 두고 API만 점진적으로 분리하는 Strangler Fig 패턴을 선택했습니다. 신규 API는 Spring Boot로 새로 만들되 같은 Oracle DB를 바라보게 두고, 화면이 신규 API를 호출하도록 하나씩 옮기는 방식으로 진행하고 있습니다.
- 검색이나 동기화처럼 트랜잭션 성격이 다른 워크로드는 PostgreSQL(pg_trgm)로 분리해 듀얼 DataSource 구조로 가져갔습니다. Spring AbstractRoutingDataSource로 워크로드에 따라 분기하도록 했고, 각 도메인 API는 JPA + QueryDSL + MyBatis(복잡 쿼리·집계) 조합으로 재설계했습니다.
- 검색성 조회는 PostgreSQL 쪽으로 분리해 기존 Oracle에 걸리는 부담을 줄였고, 화면 단위로 점진 교체할 수 있는 흐름이 자리잡았습니다.
도메인 API 설계·구현 (법령·판례 콘텐츠)
- 변경 이력이 있는 콘텐츠와 연관 자료를 다루는 영역이라, 시점 기준 조회와 연관 콘텐츠 조회를 API 설계의 기본 축으로 잡았습니다.
- 레거시 Oracle의 법령·판례·세무자료 데이터를 화면 요구사항에 맞는 응답 구조로 재구성했고, 시점 기준 메타 조회와 조문 단위 연관 매핑 조회를 책임 단위로 분리해 엔드포인트를 정리했습니다.
- Spring REST Docs를 적용해 컨트롤러 테스트에서 API 문서가 함께 생성되도록 했고, 문서가 코드 변경을 따라오지 못하는 문제를 줄였습니다.
- 이 패턴(시점 + 참조)이 자리잡은 이후, 다른 백엔드가 자기 도메인을 만들 때도 같은 흐름을 따라가게 됐습니다.
API Gateway · 인증/인가 책임 경계 재설계
- 초기 구조에서는 Gateway가 모든 자원의 권한 정책을 알고 있어야 했는데, 자원 서비스가 늘어날수록 Gateway 필터가 도메인 변경을 따라가야 하는 결합이 커지는 문제가 있었습니다. 필터 한두 곳을 보완하는 수준이 아니라, 책임 경계 자체를 다시 잡아야 한다고 판단했습니다.
- Gateway는 인증만 책임지고 인가는 자원 서비스에서 판단하도록 책임을 나눴습니다. Gateway는 별도 IdP 서비스가 발급한 RS256 JWT를 검증하고 헤더로 사용자 컨텍스트를 넘기는 역할까지만 가지도록 정리했고, 권한 어휘는 사내 공통 라이브러리의 enum에서 단일 정의하도록 했습니다.
- 운영자(백오피스) 인증은 외부 고객용 IdP와 분리해 LDAP 기반으로 처리했습니다. 외부 고객 인증 쪽 이슈가 운영자 채널까지 번지지 않도록 하기 위한 결정이었습니다.
- 그 결과 자원 서비스의 권한 정책 변경이 Gateway 수정으로 이어지는 일을 줄였고, 서비스마다 권한명이 달라지는 문제도 공통 라이브러리 기준으로 정리할 수 있었습니다.
Kubernetes GitOps 파이프라인 구축
- 초기에는 Jenkins에서 SSH로 서버에 접속해 배포하는 방식이었습니다. 자동화는 되어 있었지만 롤백 기준이 약했고, 환경마다 같은 결과물이 배포된다는 보장도 부족했습니다. K8s 전환 시점에 맞춰 빌드와 배포 책임을 분리하는 방향으로 파이프라인을 다시 설계했습니다.
- Jenkins는 빌드와 이미지 push까지만 담당하고, 클러스터 동기화는 ArgoCD가 매니페스트 repo를 pull 방식으로 감시하도록 구성했습니다. 앱 코드 repo와 매니페스트 repo를 분리했고, 환경별 차이는 Kustomize overlay에서만 표현하도록 정리했습니다.
- 처음에는 환경별로 다시 빌드하는 구조였지만, staging/prod 파이프라인을 설계하는 과정에서 같은 소스가 같은 결과물로 배포된다는 보장이 약하다는 문제가 보였습니다. 이후 dev에서 한 번 빌드한 이미지를 staging/prod로 promote하는 방식으로 바꿨습니다.
- ArgoCD는 App-of-Apps 패턴으로 구성해 신규 서비스 추가 시 정해진 매니페스트 묶음을 추가하면 배포 흐름에 올라오도록 했습니다. 관측성 같은 인프라성 컴포넌트도 같은 GitOps 흐름 위에서 관리했고, 배포 이력과 환경별 차이는 Git 기준으로 추적할 수 있게 됐습니다.
중앙 로그 수집 파이프라인 구축
- K8s 전환 직후에는 ArgoCD UI에서 Pod 로그를 확인했지만, Pod가 재시작되면 로그가 사라지고 서비스 간 흐름을 이어서 보기 어려웠습니다. SSH 배포 시기부터 필요성을 이야기했던 중앙 로그 수집을 K8s 전환 이후 다시 추진했습니다.
- 스택은 Elasticsearch 기반으로 잡았습니다. 사내에 다른 용도로 운영 중인 Elasticsearch가 있어 클러스터를 새로 띄우지 않고 인덱스를 추가하는 방식으로 시작할 수 있었고, 이 덕분에 초기 도입 비용을 줄일 수 있었습니다.
- 초기 권고안은 Logstash + logback 조합이었지만, 프로젝트는 log4j2를 사용하고 있어 그대로 적용하기 어려웠습니다. 모든 서비스의 로깅 설정을 크게 바꾸기보다, 컨테이너 stdout 로그를 인프라 쪽에서 수집하는 방향으로 바꿔 Filebeat DaemonSet을 선택했습니다.
- Filebeat가 노드별 컨테이너 로그를 수집해 Elasticsearch로 직접 전송하도록 구성했고, 서비스 네임스페이스만 수집하도록 필터를 적용했습니다. dev 환경에서 먼저 수집 구조를 검증했고, staging/prod 반영을 위해 ILM, 멀티라인 처리, 로그 보관 주기 같은 운영 정책을 함께 정리하고 있습니다.
마스터 코드 캐시 콜드스타트 개선
- 공통 화면 진입 시 마스터 코드(세목·과세 구분 등) 조회가 사용자 체감 수준으로 느렸습니다. 캐시는 있었지만 첫 요청에서 DB로부터 채워지는 구조라 콜드스타트 페널티가 그대로 사용자에게 노출되고 있었습니다.
- Caffeine 메모리 캐시를 쓰면서 기동 시 워밍업을 함께 가져가기로 했습니다. 처음에는 같은 빈 안에서 워밍업 메서드를 호출했는데 Spring AOP self-invocation 문제로 캐시가 채워지지 않았고, ApplicationReadyEvent 후크에서 워밍업을 수행하는 별도 빈으로 분리해 우회했습니다.
- G1GC와 -XX:MaxRAMPercentage로 컨테이너 환경에 맞게 GC 튜닝도 같이 했습니다.
- 마스터 코드 콜드스타트가 사용자가 체감하지 못할 수준으로 단축됐고, Full GC 빈도가 줄어 Pod 메모리가 cgroup 한계 안에서 안정적으로 유지됐습니다.