1. 프로젝트 개요
- 프로젝트 목적: 가볍고 빠른 외부 API 연동 금융 분석 도구
- 프로젝트 핵심 기능: 금융위원회 OpenAPI 연동, 지수 정보 및 데이터 제공, 시각화 대시보드, 자동 연동 스케줄링
2. 담당한 작업
- R&R: 지수 데이터 관련 담당
- 공통 작업
1. 스키마 및 ERD 작성
- 개인 작업
1. 지수 데이터 CRUD 구현
2. 지수 데이터 다운로드(CSV) 구현
3. 기술적 성과
- 기술스택
- Framework: Spring Boot
- Database & ORM: PostgreSQL, Spring Data JPA
- Documentation: springdoc-openapi (Swagger)
- Scheduling: Spring Scheduler
- Utility: MapStruct, QueryDSL
- 배포 및 협업: Railway.io / Git & GitHub / Discord
- 구현한 주요 기능
- 지수 데이터 엔티티 캡슐화 및 무결성 확보
- 캡슐화: 생성자 접근 제어를 통해 외부에서 객체를 무분별하게 생성하는 것을 차단하고, Setter를 제거하고 정적 팩토리 메서드와 명확하게 수정 가능한 값만 update하는 메서드를 통해 캡슐화했습니다.
- 무결성 확보: 정적 팩토리 메서드와 업데이트 메서드에서 유효값 검증 로직을 추가해서 엔티티가 자체적으로 유효하지 않은 상태로 존재할 수 없도록 하였습니다.
- 지수 데이터 Export
- 메모리 오버헤드 방지: 대량의 데이터를 CSV 문자열로 변환할 때 StringBuilder를 활용하여, 불필요한 String 객체 생성을 막고 메모리 사용을 최적화했습니다.
- 외부 라이브러리 의존도 최소화: OpenCSV 등의 외부 프레임워크 없이 자체 포맷팅 로직을 구현하여 프로젝트의 무게를 줄였습니다.
- 인터페이스 추상화 활용: 파일 다운로드 처리에 스프링의 Resource 인터페이스를 활용하여, 향후 추출 방식(메모리/파일/스트림)이 변경되어도 API 스펙이 유지되도록 유연하게 설계했습니다.
- 지수 데이터 목록 조회
- 커서 기반 페이지네이션 적용: 오프셋 기반 페이지네이션이 아닌 커서 기반 페이지네이션을 적용함으로써 데이터 개수가 많아져도 성능 저하가 없도록 하였습니다.
- 쿼리 최적화: QueryDSL을 적용하여 중복되는 코드를 최소화하고 타입 안정성을 보장하며 쿼리 성능 및 가독성을 향상시켰습니다.
4. 문제점 및 해결 과정
- 상황 (Situation): 사용자가 지수 데이터를 조회할 때, 대량의 시계열 데이터를 효율적으로 불러오고 다양한 조건(기간, 지수 식별자, 정렬 방식 등)으로 필터링할 수 있는 API가 필요했습니다
- 과제 (Task): 일반적인 오프셋(Offset) 기반 페이징은 페이지 번호가 뒤로 갈수록 DB에서 앞선 데이터를 모두 읽어야 하므로 성능 저하가 우려되었습니다. 또한, 다양한 검색 조건을 Spring Data JPA의 기본 메서드 이름이나 @Query로 처리하기에는 조건 조합의 수가 너무 많아 유지보수가 어려웠습니다.
- 행동 (Action):
- 성능 개선을 위해 **커서 기반 페이징(Cursor-based Pagination)**을 도입했습니다.
- 복잡한 동적 쿼리를 깔끔하게 처리하기 위해 QueryDSL을 프로젝트에 적용했습니다. IndexDataQueryCondition이라는 하나의 DTO로 요청을 받고, indexInfoIdEq, dateRange 등 BooleanExpression을 반환하는 메서드들을 분리하여 조건 유무에 따라 동적으로 WHERE 절이 구성되도록 했습니다.
- 정렬 기준 필드의 값이 동일할 때 발생할 수 있는 페이징 누락(데이터 건너뜀) 현상을 방지하기 위해 orderBy 절에 고유 식별자(indexData.id)를 2차 정렬 조건으로 추가했습니다.
- 결과 (Result): 데이터량의 증가와 관계없이 전체 데이터를 스캔하지 않고 인덱스를 활용하여 일정한 응답 속도를 보장하는 커서 기반 페이징을 구현했습니다. 또한, 검색 조건을 QueryDSL의 BooleanExpression으로 모듈화하여, 새로운 조건이 추가되더라도 where 절에 단 한 줄의 코드만 추가하면 되는 높은 확장성과 가독성을 확보했습니다.
5. 협업 및 피드백
- 소통을 통한 충돌 관리 전략 수립: 협업 과정에서 Git을 통한 버전 관리의 중요성을 실감했습니다. 특히 병합(Merge) 시 발생하는 충돌을 경험하며, 기술적인 해결보다 '수정 전 소통'이 가장 저비용으로 충돌을 방지하는 전략임을 배웠습니다. 작업 시작 전 수정 범위와 영향도를 공유하는 문화를 통해 충돌 발생 자체를 최소화하는 협업 체계를 구축했습니다.
- 코드 리뷰를 통한 관점의 확장: 팀원들과의 코드 리뷰를 통해 제가 놓치고 있던 다양한 시각을 접할 수 있었습니다. 동료의 코드를 리뷰하며 전체적인 프로그램의 흐름(Flow)을 파악하는 문해력을 길렀고, '좋은 코드'에 대한 기준을 동료들과 맞추어 나가며 가독성 높은 코드를 작성하는 계기가 되었습니다.
6. 코드 품질 및 최적화
프로젝트 중 코드의 가독성과 유지보수성을 어떻게 고려했는지, 성능 최적화를 위한 작업을 설명해 주세요.
- 객체지향적 설계를 통한 유지보수성 향상:
- Enum을 활용한 다형성(Strategy) 적용: 동적 정렬 및 커서 페이징 로직을 구현할 때, QueryDSL 내부에 복잡한 switch나 if-else 분기를 두지 않고 IndexDataSortField Enum에 로직을 위임(cursorCondition(condition))했습니다. 이를 통해 새로운 정렬 기준이 추가되어도 기존 Repository 코드를 수정할 필요가 없는 **개방-폐쇄 원칙(OCP)**을 완벽히 준수했습니다.
- 관심사 분리 (MapStruct 도입): 서비스 레이어(Service)가 비즈니스 로직에만 집중할 수 있도록, DTO와 Entity 간의 데이터 변환 책임을 **IndexDataMapper로 완전히 분리(SRP)**하여 코드의 응집도를 높이고 결합도를 낮추었습니다.
- 다형성을 활용한 확장성 설계: Export 기능 구현 시 반환 타입을 Resource 인터페이스로 추상화하여, 향후 대용량 데이터 추출 시 스트리밍 방식(InputStreamResource) 등으로 구현을 변경하더라도 Controller의 API 스펙을 수정할 필요가 없는 유연한 구조를 마련했습니다.
- 캡슐화 및 데이터 무결성 강화: 엔티티 설계 시 @NoArgsConstructor(access = AccessLevel.PROTECTED)와 정적 팩토리 메서드(create)를 사용하여 무분별한 객체 생성을 막고, 무상태 변경을 방지하기 위해 Setter를 철저히 배제했습니다.
- 성능 최적화를 위한 쿼리 전략:
- DTO 직접 조회(Projections)를 통한 N+1 원천 차단: 일반적으로 JPA에서 Entity를 조회한 후 DTO로 변환하는 과정에서 연관관계 객체(IndexInfo)를 참조할 경우 N+1 쿼리 문제가 발생할 수 있습니다. 이를 완벽하게 방지하기 위해, QueryDSL의 **Projections.constructor**를 사용하여 DB 쿼리 단계에서부터 꼭 필요한 컬럼(indexInfo.id 포함)만 선택하여 DTO로 바로 매핑(Select Projection) 하도록 설계했습니다. 그 결과 N+1 문제가 전혀 발생하지 않으며 불필요한 엔티티 영속화로 인한 메모리 낭비도 최적화했습니다.
- 최적화된 쿼리 실행 제어:
- Export API: 대량의 데이터를 추출할 때 불필요한 연관관계 조회를 배제하고, findAllForExport 메서드를 통해 단 1회의 쿼리로 모든 필수 데이터를 가져오도록 제어했습니다.
- 페이징 API: 목록 조회 쿼리 1회와 조건부 count 쿼리 1회, 총 2회의 예측 가능한 쿼리만 발생하도록 최적화하여 데이터베이스 부하를 최소화했습니다.
- 코드 가독성 향상을 위한 전략:
- 표준 컨벤션 기반의 일관된 코드 스타일 유지: Google Java Style Guide를 적용하여 팀원 간의 코드 포맷팅을 통일했습니다. 이를 통해 불필요한 포맷팅 관련 코드 리뷰 시간을 줄이고, 로직의 흐름을 읽는 데 집중할 수 있는 환경을 만들었습니다.
- API 명세 인터페이스 분리 (Controller 경량화): Swagger(@Operation, @ApiResponse 등) 및 유효성 검사 어노테이션이 Controller의 본질적인 로직을 가려 가독성을 떨어뜨리는 문제를 해결하기 위해, IndexDataApi 인터페이스를 별도로 추출했습니다. 문서화에 대한 책임은 인터페이스에 위임하고, 구현체인 Controller는 순수하게 HTTP 요청 매핑과 비즈니스 로직 호출에만 집중하게 하여 코드의 가독성을 압도적으로 높였습니다.
7. 향후 개선 사항 및 제안
- Export 시 대용량 데이터 처리 아키텍처 고도화: 현재는 수만 건 수준의 데이터를 메모리 내에서 처리하고 있으나, 향후 수백만 건 이상의 데이터 폭증을 대비하여 StreamingResponseBody를 도입한 실시간 스트리밍 Export로 리팩토링이 필요합니다.
- 비동기 처리 도입: OpenAPI 연동이나 무거운 파일 생성 등이 메인 서버 스레드를 점유하지 않도록, 메시지 큐를 활용하여 비동기 백그라운드로 분리하고 완료 시 알림을 보내는 방식의 아키텍처 개선이 필요합니다.
- Export 포맷 다변화: 현재 CSV 형식만 지원하는 기능을 확장하여 Excel(XLSX), JSON 등 사용자의 니즈에 맞는 다양한 파일 포맷 선택 옵션을 제공할 수 있습니다.