Post

[ECC 2026 Summer Project] SplitOpt - Week 6

[ECC 2026 Summer Project] SplitOpt - Week 6

기간: 2026. 08. 17 – 08. 23 (6주차)

내용: 스키마 드리프트 검증, 시간대 고정, 예측 계산 수정, 인가 구멍 마감, 발표 자료

파트: 백엔드


기능 구현은 5주차에 대부분 끝났고, 이번 주에 고친 것은 요청이 200으로 처리되면서 값만 틀리는 종류였다. 주말에는 최종 발표 자료를 만들었다.

1. 엔티티와 운영 스키마 상시 검증

5주차에 지출 수정이 운영에서만 실패한 원인이 환경별 스키마 차이였다. 테스트와 로컬은 엔티티로 스키마를 만들고(ddl-auto) 운영만 Flyway 마이그레이션을 쓰므로, 둘이 어긋나도 어떤 테스트에도 걸리지 않고 운영에서만 드러난다. 같은 이유로 두 번 문제가 발생했다.

엔티티에 제약을 추가할 때 마이그레이션을 함께 쓰는 것은 사람이 기억해야 하는 절차라, 검증을 테스트로 옮겼다. Testcontainers로 MySQL 8을 띄워 Flyway를 적용한 뒤 Hibernate validate로 엔티티와 대조한다. 어긋나면 스프링 컨텍스트가 뜨지 않아 테스트가 실패한다. 도커가 없는 환경에서는 자동으로 건너뛰게 해, 팀원 로컬이나 CI가 도커 유무로 막히지 않게 했다.

2. 서버 기준 시각 고정

배포 이미지(eclipse-temurin)가 UTC로 실행되고 있었다. 일정 시작 시각처럼 API로 주고받는 값은 시간대가 없는 벽시계 시각이고 사용자는 이를 한국 시각으로 입력하는데, 서버만 UTC로 동작하면 비교 시점에 9시간이 어긋난다.

  • 예산 초과 예측(40): “일정이 시작됐는가” 판정이 한국 기준 오전 9시에 넘어갔다. 여행 첫날 아침에는 예측이 나오지 않고, 여행 중에도 매일 0~9시 사이에는 경과 일수가 하루 적게 잡혀 예상 총액이 부풀려진다.
  • 기록 시각 전반: 정산 완료·참여·초대 만료 등이 9시간 이르게 내려간다. 시각을 담는 응답 DTO가 12개다.

Dockerfile의 TZ만 고쳐도 배포는 정상이 되지만, 그렇게 두면 로컬·CI·배포가 서로 다른 시각으로 동작해 같은 코드가 환경마다 다른 결과를 낸다. 애플리케이션에서 기준 시각을 고정하고 컨테이너 TZ도 함께 맞춰 로그 시각이 애플리케이션과 어긋나지 않게 했다. 수정을 제거하면 UTC 환경에서 테스트가 실패하는 것까지 확인했다.

3. 여행 전 지출을 예측에서 분리

예산 초과 경고가 실제보다 과하게 나오는 문제가 있었다. 항공권·숙소처럼 여행 전에 미리 결제한 금액까지 “지금까지 쓴 돈”에 넣고 경과 일수로 나눠 하루 평균을 구하고 있었기 때문이다.

1
2
3
항공권 100만(여행 전) + 첫날 식비 10만, 5일 중 2일 경과
이전: 110만 ÷ 2일 × 5일 = 275만  (예산 200만 → 초과 경고)
이후: 100만 + (10만 ÷ 2일 × 5일) = 125만  (초과 아님)

확정된 지출은 그대로 더하고 기간 중 지출만 속도로 환산하도록 나눴다. 여행 전 지출이 없으면 결과는 이전과 같다.

기간 시작 경계는 시각이 아니라 날짜로 잡았다. 경과 일수를 날짜 단위로 세는데 경계만 시각으로 자르면, 첫 일정이 09시에 시작할 때 같은 날 00시로 기록된 지출이 “여행 전”으로 빠져 첫날 지출이 통째로 사라진다. 한 계산의 두 부분이 서로 다른 단위를 쓰면 그 사이에서 값이 누락된다.

응답에는 spentBeforePeriod를 추가해 다음 등식을 확인할 수 있게 했다.

1
projectedTotal = spentBeforePeriod + dailyAverage × totalDays

결과 숫자만 내려주면 값이 이상할 때 어느 항이 틀렸는지 알 수 없다.

4. 확인되지 않은 송금과 모임 탈퇴

참여자 삭제·탈퇴 차단 조건이 잔액 0 여부였는데, 이 잔액이 SENT까지 상계하고 있었다.

SENT는 보내는 사람이 스스로 표시한 상태로 받는 사람의 확인 전이고, CANCEL로 PENDING에 되돌릴 수도 있다. 따라서 실제로 송금하지 않고 표시만 해도 잔액이 0이 되어 모임을 나갈 수 있었고, 나간 뒤에는 받을 사람이 사라진 정산만 남는다. 판정을 COMPLETED만 상계한 잔액으로 바꿔, 받는 사람이 확인해야 나갈 수 있게 했다.

잔액 조회(23)와 최적화(24)는 그대로 SENT를 상계한다. 그쪽 목적은 이미 보낸 돈에 대해 송금 건을 다시 만들지 않는 것이라 보낸 시점부터 빼는 것이 맞다. 같은 “잔액”이 두 곳에서 다른 기준을 쓰므로 주석으로 이유를 남겼다.

5. 비율 단위와 입력 검증

정산 요약(29)의 완료율이 0.0~1.0 비율로 내려가고 있었다. 명세는 퍼센트이고 같은 서비스의 예산 사용률도 퍼센트라, 화면이 퍼센트로 표시하면 50%가 0.5%로 보인다. 같은 서비스 안에서 비율 단위가 섞이면 어느 화면에서든 틀린 값이 나오므로 퍼센트로 통일했다.

일정 종료 일시가 시작보다 앞서도 그대로 저장되는 문제도 함께 막았다. 기간 길이가 0 이하가 되면 예산 초과 예측이 오류 없이 “예측 불가”로 빠지고, 화면에는 설명 없이 예측만 사라진다. 등록(33)과 수정(35)이 같은 DTO를 쓰므로 양쪽 모두 거부된다. 종료 시각이 없는 경우와 시작과 같은 시각은 그대로 허용한다.

6. 권한 범위 조정

지출 수정·삭제를 결제자 본인으로 제한한 것은 3주차 회의 결정이다. 그런데 일정 상세 화면에서 지출을 연결하려면 다른 사람이 등록한 지출도 일정에 붙일 수 있어야 했다.

해당 결정의 근거는 다른 사람이 지출을 고쳐 정산이 틀어지는 것을 막는 데 있었고, 일정 연결은 여기에 해당하지 않는다.

  • 정산·통계는 Schedule을 참조하지 않는다
  • 예산은 일정을 기간 산정에만 쓰고, 사용액은 모임 전체 지출 합계라 연결 여부와 무관하다
  • 영향 범위는 일정별 지출 조회(37)뿐이고, 잘못 연결해도 참여자 누구나 다시 옮길 수 있다

연결 변경만 별도 API(PATCH .../expenses/{id}/schedule)로 분리하고 권한을 모임 참여자 전체로 넓혔다. 제목·금액을 바꾸는 수정(20)과 삭제(21)는 그대로 결제자 본인만 가능하다. 규칙을 문자 그대로 적용하는 것과 규칙이 지키려는 대상을 지키는 것이 갈리는 경우라, 근거를 커밋 메시지와 PR에 적어 팀 확인을 받았다.

반대로 좁힌 것도 있다. 통계(30~32)와 일정(33~37) API가 경로에서 groupId만 받으면서 참여 여부를 검증하지 않고 있었다. 5주차에 내 파트에는 GroupAccessGuard를 붙였지만 다른 파트는 그대로였다. 두 컨트롤러에도 같은 가드를 걸어 기준을 맞췄고, 일정 목록(34)이 없는 모임에 빈 목록으로 200을 주던 것도 404로 바뀌었다. 파트를 나눠 개발하면 이렇게 어느 파트에도 속하지 않는 공통 규칙에 공백이 생긴다.

7. 최종 발표 자료

주말에 발표 자료를 만들었다. 이번 프로젝트에서 고친 문제를 정리하면 대부분 한 종류로 묶인다.

증상원인
같은 청구가 두 벌 저장동시 요청이 서로의 미커밋 결과를 보지 못함
정산에 구멍이 생김확인되지 않은 송금을 남기고 탈퇴
예산 경고가 과함여행 전 지출이 하루 평균에 섞임
날짜 집계가 하루 밀림서버 기준 시각이 UTC
운영에서만 스키마 오류마이그레이션 누락이 로컬에서 드러나지 않음

다섯 건 모두 기능이 동작하지 않는 버그가 아니라 값이 조용히 틀리는 문제다. 발표도 이 축으로 구성했다.

수치로는 API 40개 전부 구현, 테스트 287개, main 실패 테스트 0개다. 4주차에 63개였던 테스트가 늘어난 것은 기능이 늘어서가 아니라 위 다섯 건을 고칠 때마다 회귀 테스트를 함께 붙였기 때문이다.

최종 발표는 다음 주 금요일이다.

This post is licensed under CC BY 4.0 by the author.