본문 바로가기
Project/Project

[Findex] 개인 리포트

by boolynn 2026. 3. 20.

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
    • 코드 리뷰
      • 팀원 최소 2명씩 코드 리뷰를 한 후에 PR을 merge 하도록 규칙 설정
          • 보다 상세한 코드 리뷰가 가능했고, merge 이후 추가 수정해야 하는 상황이 줄어듦
          • 사소한 수정사항에도 팀원들의 리뷰를 기다려야 하는 불필요한 상황이 발생하기도 함
          • 코드 리뷰 링크: https://github.com/sb10-Findex-4/sb10-findex-team04/pull/68
    • 회의
      • 매일 아침 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. 문제점 및 해결 과정 중, 연동 작업 관련 문제 상황에서 확인 가능

7. 향후 개선 사항 및 제안

  • 대시보드 지수 차트 데이터의 캐싱 도입
    • 현재 상황: 한 번 생성되면 당일 내에는 변하지 않는 특성에도 불구하고, 사용자가 접속할 때마다 반복적으로 DB를 조회함
    • 개선 사항: 캐시를 도입해 빈번하게 조회되는 차트 데이터를 저장, DB 접근을 최소화해 성능을 더욱 높이고 싶음

8. 느낀 점

조금 더 러프한 작성을 위해 회고록에 이어 작성하겠다.

'Project > Project' 카테고리의 다른 글

[모뉴] 개인 개발 리포트  (2) 2026.05.08