1. 프로젝트 개요

Findex는 금융 지수 데이터를 한눈에 확인할 수 있게 하는 대시보드 서비스이다.
조금 더 자세히 설명하면, 금융위원회의 지수 데이터를 API를 활용해
금융 지수 데이터 연동 → DB 저장 → 알아보기 쉽게 가공 → 대시보드 제공
하는 흐름으로 구성된 서비스라고 할 수 있다.
2. 담당한 작업
주요 담당 작업으로는 연동 작업과 대시보드 관리 구현을 맡았다.
- 연동 작업(SyncJob)
- 연동 작업 Entity 및 DTO 설계
- 연동 작업 결과 생성 및 반환 로직 구현
- 대시보드
- 주요 지수 현황 요약 기능 구현: 즐겨찾기한 지수들을 일간/주간/월간 별로 모아 한 눈에 파악 가능하게 하는 기능 구현
- 지수 차트 기능 구현: 종가 지수와 5일/20일 이동 평균선(기간 내 가격 평균) 차트 그래프 기능 구현
- 지수 성과 랭킹 조회 기능 구현: 전일/전주/전월 대비 성과 랭킹을 표시하는 기능 구현
- 기타
- 지수 데이터, 연동 작업 Swagger API 명세서 작성
- PR 리뷰
- 컨벤션 관리: 역할 분담을 하며 맡았으나 크게 기여한 부분은 없는 것 같다.
- 발표 자료 ppt 디자인: 어쩌다 급하게 맡게 되었다.
3. 기술적 성과
| Spring Boot | - RESTful API 서버의 중심 프레임워크로 사용 - 계층화된 아키텍쳐(Controller-Service-Repository)를 적용해 비즈니스 로직의 응집도와 유지보수성을 높임 |
| PostgreSQL | - 금융 시세 데이터의 무결성과 복잡한 관계형 데이터를 안정적으로 관리 - 대량의 지수 데이터를 인덱싱함으로써 조회 성능 최적화 - 대시보드 관리 구현 중 복잡한 쿼리 실행 및 데이터 검증에 활용 |
| Spring Data JPA | - 반복적인 CRUD 작업 및 SQL 작성 없이 객체 중심의 데이터 정합성을 유지하며 DB 접근 |
| MapStruct | - Entity와 DTO 간의 변환을 자동화 - 타입 안전성을 체크해 런타임 에러 방지 |
| Swagger | - API 명세서 작성시 사용 - @Schema 등을 활용해 필드 제약 사항과 예시 데이터를 포함한 상세한 API 문서 구현 |
4. 문제점 및 해결 과정
- 연동 작업 관련 문제 상황
- 문제 상황
- 사용자가 여러 개의 지수 ID(indexInfoIds)를 선택하고, 특정 기간을 설정하여 연동을 요청하는 상황
- 지수가 10개이고 기간이 30일이라면, 총 300개의 데이터를 생성해야 하므로 DB 부하 및 서버 속도가 느려짐
- 과제
- 이중 루프(지수 + 날짜) 내에서 발생하는 DB 접근을 최소화하여 응답 시간 단축
- 해결 방법
- 루프 내부에서 save()를 호출하지 않고, ArrayList<SyncJob>를 생성하여 모든 엔티티를 먼저 수집
- 반복문이 모두 종료된 시점에 syncJobRepository.saveAll(syncJobsToSave)를 단 한 번 호출하여 일괄 저장(Bulk Insert) 처리
- 배운 점
- 다량의 데이터 처리 시 네트워크 I/O 비용이 성능에 미치는 영향을 알게 됨
- JPA 영속성 컨텍스트의 효율적인 활용 방법을 익힘
- 관련 코드
- 문제 상황
// 3. 루프 안에서 매번 save 하지 않고 리스트에 모아서 한 번에 저장
List<SyncJob> syncJobsToSave = new ArrayList<>();
// 4. 대상 지수가 여러 개인 경우 지수별로 반복 처리 (Outer Loop)
for (Long indexId : syncJobCreateRequestDto.indexInfoIds()) {
IndexInfo indexInfo = indexInfoRepository.getReferenceById(indexId);
// 대상 날짜가 여러 개인 경우 날짜별로 반복 처리 (Inner Loop)
LocalDate currentDate = syncJobCreateRequestDto.baseDateFrom();
// ... 중략 ...
}
}
// 5. DB에 일괄 저장
Iterable<SyncJob> savedEntities = syncJobRepository.saveAll(syncJobsToSave);
- 대시보드 관련 문제 상황
- 문제 상황
- 관심 지수(즐겨찾기)의 성과 요약 데이터를 대시보드에 출력하는 기능
- LocalDate.now()를 사용해 당일 기준으로 데이터를 조회하도록 설계
- DB에 지수 데이터가 존재함에도 대시보드에 데이터가 나타나지 않는 문제 발생
- 과제
- 누락 없이 즐겨찾기한 지수들의 최신 성과 지표를 확인할 수 있도록 조회 기준일 보정
- 해결 방법
- 시스템의 지수 데이터가 외부 API로부터 다음 날 배치 작업으로 연동된다는 비즈니스 규칙을 재확인
- 조회 기준일을 LocalDate.now().minusDays(1) 즉, 전일로 보정
- 배운 점
- API를 설계할 때 단순히 코드가 맞는지를 넘어, 데이터가 생성되는 생명주기와 배치 타이밍을 고려하기
- 추가적으로, 도움을 구했을 때 다른 팀원 분이 바로 해결을 해주셨음
- → 협업 시 문제 발생 상황을 공유하고 도움을 구하는 것이 효과적임
- 관련 코드
- 문제 상황
public List<IndexPerformanceDto> getFavoriteIndexSummary(PeriodType period) {
// 1. 날짜 설정 (지수 정보는 다음 날 연동되므로 전일 기준으로 조회)
LocalDate yesterday = LocalDate.now().minusDays(1);
LocalDate baseDate = calculateRankBaseDate(period);
System.out.println("yesterday: " + yesterday);
// 2. IndexInfo 테이블에서 즐겨찾기한 지수 가져오기
List<IndexInfo> favoriteInfos = indexInfoRepository.findAll().stream()
.filter(IndexInfo::isFavorite) // favorite 필드가 true인 것만 필터링!
.toList();
// ... 생략 ...
- Swagger API 문서 작성 관련 문제 상황
- 문제 상황
- 제공받은 요구사항(기본 API 명세서)에 차트 데이터 리스트가 Object 타입으로 정의됨
- 이에 따라 DTO를 List<Object> 형식으로 구현했으나, 프로젝트 후반 문서화 작업을 진행하며 Swagger UI를 확인한 결과, 실제 데이터 구조가 노출되지 않는 문제를 발견
- 과제
- 구체적이고 명확한 데이터 타입을 정의한 API 문서 작성
- (추가적으로, 이 단계에서 급히 DTO 반환 타입을 수정했다...)
- 해결 방법
- Swagger의 @Schema 어노테이션을 사용하여 각 필드의 의미(description)와 예시 데이터(example)를 추가함으로써 문서의 완성도를 높임
- 제공된 명세서의 Object 표기가 협업에 효율적이지 않음을 인지, 이를 실제 데이터 모델인 IndexDataDto로 구체화하여 코드 수정
- 배운 점
- 정확한 명세가 빠르고 효율적인 개발에 이바지함을 알게 됨
- 문제 상황
- 깃허브 사용 문제 상황
- 문제 상황
- 깃허브 사용에 미숙해 PR merge 전 push를 날리는 실수 발생
- 의도했던 PR 이외의 push를 없애고자 rebase로 내가 작성한 커밋만 제거하려고 함
- 그 과정 중 커밋 그래프와 브랜치가 바뀌면서 히스토리가 완전히 꼬여버리고, 결국 PR이 자동 close 됨
- 과제
- 의도했던 PR 내역 develop 브랜치에 merge
- 다른 팀원들/이후 내가 작성한 커밋 유지하되, 리뷰 이전에 머지되지 않도록 함
- 해결 방법
- 히스토리가 너무 꼬여서 깃 명령어를 통해서는 해결하기 어려운 상황이라고 판단
- 새로운 브랜치를 파서 최신화 후에 로컬에 백업해둔 코드들을 복붙
- 배운 점
- PR merge 전 동일 브랜치에서 push 날리지 않기
- 로컬 코드 주기적으로 백업하기
- 문제 상황
5. 협업 및 피드백
- 팀의 협업
- 초기 프로젝트 세팅
- 커밋, PR, 코드 컨벤션 규칙을 상세하게 정하고 프로젝트를 시작함
- 상세한 규칙 덕분에 불필요한 충돌을 방지할 수 있었음
- 가독성이 좋은 코드를 완성할 수 있었음
- 초반에는 상세한 규칙 때문에 PR을 날리는 것에 부담을 느끼는 등, 어려움이 있었지만 프로젝트를 진행하며 차차 나아짐
- PR 링크: https://github.com/sb10-Findex-4/sb10-findex-team04/pull/58
- 커밋, PR, 코드 컨벤션 규칙을 상세하게 정하고 프로젝트를 시작함
- 코드 리뷰
- 팀원 최소 2명씩 코드 리뷰를 한 후에 PR을 merge 하도록 규칙 설정
- 보다 상세한 코드 리뷰가 가능했고, merge 이후 추가 수정해야 하는 상황이 줄어듦
- 사소한 수정사항에도 팀원들의 리뷰를 기다려야 하는 불필요한 상황이 발생하기도 함
- 코드 리뷰 링크: https://github.com/sb10-Findex-4/sb10-findex-team04/pull/68
- 팀원 최소 2명씩 코드 리뷰를 한 후에 PR을 merge 하도록 규칙 설정
- 회의
- 매일 아침 9시, 저녁 6시 총 2번의 회의를 진행함
- 대다수가 처음으로 임하는 프로젝트이고, 내성적인 팀원이 많아 활발한 회의가 이루어지지는 않았다는 아쉬움이 있음
- 그럼에도 진행상황을 꾸준히 공유하고, 문제 상황을 함께 해결해나갈 수 있다는 점에서 의미가 있었음
- 매일 아침 9시, 저녁 6시 총 2번의 회의를 진행함
- 초기 프로젝트 세팅
- 팀원 간 협업: 우연하게도 연동 작업, Swagger API 문서 작업을 모두 팀원 분과 나눠 진행하게 되어서, 협업하는 법을 진~하게 배울 수 있었다.
- 상세한 역할 분담의 중요성
- 작업을 진행하기 전 사소한 부분에서부터 상세하게 역할 분담을 하고, 서로가 이해한 것이 동일한지 확인하는 것이 중요
- 문제 상황 발생 시 적극적인 공유
- 작업 중 문제가 발생했을 때, 팀원 또한 같은 문제 상황을 겪을 확률이 높음
- 발생한/해결한 문제 상황을 함께 작업하는 팀원에게 공유하면 불필요한 문제 해결 시간을 줄일 수 있음
- 작업 상황의 적극적인 공유
- 작업 상황을 공유함으로써 충돌을 방지하거나, 미리 인지하고 작업을 진행할 수 있음
- 상세한 역할 분담의 중요성
6. 코드 품질 및 최적화
- 유지보수성 및 코드의 가독성
- Layered Architecture 준수: Controller - Service - Repository 관심사 분리
- 주석
- 덜할 바에는 과한 것이 나음
- 메소드 전체 기능, 로직 별 단계가 드러나도록 숫자를 붙여 주석 작성
/*
연동 결과 생성 및 반환
*/
public List<SyncJobDto> createSyncJob(SyncJobCreateRequestDto syncJobCreateRequestDto, String clientIp) {
// 1. 제약 조건: 날짜 유효성 검사 (시작일이 종료일보다 뒤면 에러)
if (syncJobCreateRequestDto.baseDateFrom().isAfter(syncJobCreateRequestDto.baseDateTo())) {
throw new IllegalArgumentException("시작 날짜가 종료 날짜보다 미래일 수 없습니다.");
}
// 2. 작업자 정보 결정: IP가 있으면 IP 저장, 없으면 배치가 실행한 'system'으로 저장
String worker =
(syncJobCreateRequestDto.worker() == null || syncJobCreateRequestDto.worker().isBlank())
? "system"
: syncJobCreateRequestDto.worker();
// 3. 루프 안에서 매번 save 하지 않고 리스트에 모아서 한 번에 저장
List<SyncJob> syncJobsToSave = new ArrayList<>();
// 4. 대상 지수가 여러 개인 경우 지수별로 반복 처리 (Outer Loop)
for (Long indexId : syncJobCreateRequestDto.indexInfoIds()) {
IndexInfo indexInfo = indexInfoRepository.getReferenceById(indexId);
// 대상 날짜가 여러 개인 경우 날짜별로 반복 처리 (Inner Loop)
LocalDate currentDate = syncJobCreateRequestDto.baseDateFrom();
while (!currentDate.isAfter(syncJobCreateRequestDto.baseDateTo())) {
// 엔티티 생성: 넘겨받은 연동 결과와 식별된 정보를 조합
SyncJob syncJob = SyncJob.builder()
.jobType(JobType.valueOf(syncJobCreateRequestDto.jobType())) // 문자열 타입을 Enum으로 변환
.targetDate(currentDate) // 루프 중인 현재 날짜 설정
.worker(worker) // 추출된 작업자 정보 설정
.jobTime(LocalDateTime.now()) // 현재 작업 일시 기록
.result(JobResult.SUCCESS) // 기본적으로 성공으로 기록
.indexInfo(indexInfo) // 지수 엔티티 연결
.build();
// 저장용 리스트에 추가
syncJobsToSave.add(syncJob);
// 다음 날짜로 이동
currentDate = currentDate.plusDays(1);
}
}
// 5. DB에 일괄 저장
Iterable<SyncJob> savedEntities = syncJobRepository.saveAll(syncJobsToSave);
// 6. 저장된 결과를 DTO 리스트로 변환하여 반환
return StreamSupport.stream(savedEntities.spliterator(), false)
.map(SyncJobDto::from)
.toList();
}
- 성능 최적화
- Bulk Insert를 통한 DB 접근 최소화
- 지수 데이터 연동 시 데이터를 루프 내에서 저장하지 않고, saveAll() 메소드를 사용하여 한 번에 저장
- 관련 코드는 4. 문제점 및 해결 과정 중, 연동 작업 관련 문제 상황에서 확인 가능
- Bulk Insert를 통한 DB 접근 최소화
7. 향후 개선 사항 및 제안
- 대시보드 지수 차트 데이터의 캐싱 도입
- 현재 상황: 한 번 생성되면 당일 내에는 변하지 않는 특성에도 불구하고, 사용자가 접속할 때마다 반복적으로 DB를 조회함
- 개선 사항: 캐시를 도입해 빈번하게 조회되는 차트 데이터를 저장, DB 접근을 최소화해 성능을 더욱 높이고 싶음
8. 느낀 점
조금 더 러프한 작성을 위해 회고록에 이어 작성하겠다.
'Project > Project' 카테고리의 다른 글
| [모뉴] 개인 개발 리포트 (2) | 2026.05.08 |
|---|