8주간 달려온 최종 프로젝트가 끝났다. 발표를 마치고, 22기 수료증을 받았다.
개발이 어느 정도 마무리되고 QA 단계에 접어들면서 숨어있던 버그들이 하나씩 수면 위로 올라오기 시작했다.
이번 회고는 기능 개발 이야기보다는, 마지막 스프린트에서 "왜 이게 안 돼?" 를 파고든 3일의 기록에 가깝다.
Dockerfile에 패키지 설치 한 줄을 추가했다.
dockerfileRUN apt-get update && apt-get install -y fonts-nanum fontconfig \
&& rm -rf /var/lib/apt/lists/*
fonts-nanum을 설치하면 /usr/share/fonts/truetype/nanum/NanumGothic.ttf가 생성되고,
서비스의 resolveFontPath()가 탐색하는 경로와 정확히 일치해서 즉시 해결됐다.
로컬에서 되는데 도커에서 안 된다면, 환경 차이부터 의심하자.
개발 환경과 배포 환경의 차이를 줄이는 것이 얼마나 중요한지 다시 한번 느꼈다.
앞으로 폰트, 타임존, 로케일처럼 OS 의존성이 있는 부분은 처음부터 Dockerfile에 명시해두는 습관을 들여야겠다.
컨택 리스트 페이지에서 8명이 있어야 할 목록이 16명으로 표시됐다. 모두 중복이었다.
원인
Master 서비스의 Buyer 등록 이벤트를 받아 syncToActivityContacts로 Contact를 생성하는 흐름에서,
재시도나 플래키(flaky) 응답으로 같은 (writerId, email) 조합의 Contact가 두 번 저장되는 경우가 있었다.
API 자체에 멱등성(Idempotency)이 없었던 것이다.
ContactRepository에 중복 체크 메서드를 추가했다.
javaboolean existsByWriterIdAndContactEmail(Long writerId, String contactEmail);
ContactCommandService.createContactInternal()에서 저장 전에 pre-check를 수행해,
이미 동일한 (writerId, email) 조합이 있으면 저장을 건너뛴다.
javaif (contactRepository.existsByWriterIdAndContactEmail(writerId, email)) {
return; // 이미 존재하면 스킵
}
이미 운영 DB에 쌓인 중복 행은 코드만으로는 해결되지 않는다.
별도 일회성 정리 SQL이 필요하다. 이 부분은 팀원과 함께 진행했다.
외부 시스템에서 호출되는 API는 항상 멱등성을 고려해야 한다.
특히 마이크로서비스 환경에서 서비스 간 통신은 네트워크 불안정으로 재시도가 빈번하게 발생한다.
"한 번만 호출될 거야"라는 가정 자체가 위험했다.
프론트 메일 이력 페이지에서 발송일 컬럼이 전부 빈 셀로 표시됐다.
Documents 서비스가 메일 발송 직후 Activity 서비스로 POST /api/email-logs/internal을 호출해
발송 이력을 기록하는 구조인데, EmailLogInternalRequest.toEntity()에서 emailSentAt 필드를
채우는 로직이 누락되어 있었다. 결과적으로 DB에 sentAt = NULL로 저장됐다.
toEntity() 내부에서 상태에 따라 분기 처리했다.
java// status가 SENT인 경우만 현재 시각으로 기록
// FAILED는 NULL 유지 — 재전송 UI에서 "발송 시각 없음"을 구분하기 위함
.emailSentAt(status == EmailStatus.SENT ? LocalDateTime.now() : null)
Documents 서비스가 MimeMessage.send() 성공 직후 바로 내부 API를 호출하므로,
수신 시각을 실 발송 시각으로 간주해도 무방하다고 판단했다.
배운 점
DTO의 toEntity() 변환 메서드도 꼼꼼히 검토해야 한다.
컨트롤러, 서비스 로직에만 집중하다 보니 DTO 변환 레이어에서 필드가 빠지는 걸 놓쳤다.
@NotNull이나 테스트 코드가 커버하지 않는 "선택적 필드" 에서 이런 실수가 나오기 쉽다.
이렇게 마지막 버그들을 잡아낸 뒤, 팀원들과 함께 PPT를 정리했고 내가 발표를 맡아 진행했다.
8주간의 작업이 30분 남짓의 발표로 압축되는 순간은 묘했다. 슬라이드 한 장 한 장 뒤에 며칠씩의 시행착오가 있다는 걸, 정작 발표를 끝내고 나서야 실감했다.
그렇게 발표를 마치고, 한화시스템 Beyond SW캠프 22기를 수료했다.
이번 기간은 QA가 개발만큼 중요하다는 걸 몸으로 느낀 시간이었다.
코드 품질이 높아도 운영 환경 차이, 서비스 간 재시도, DTO 변환 누락처럼
로컬 테스트에서 잡기 어려운 문제들이 분명히 존재한다.
세 버그의 공통점을 꼽으면 이렇다.
앞으로 새 기능을 만들 때, 이 세 가지를 체크리스트처럼 떠올리는 습관을 들이려 한다.
수료가 끝이 아니고 시작이라고 생각합니다. 앞으로는 "동작하는 코드"를 넘어 운영 가능한 코드를 쓰는 개발자가 되고 싶습니다. 최종 프로젝트를 진행하면서 많이 배운것도 있지만 어느 부분이 부족한지 알 수 있는 시간이였습니다. 앞으로 자소서나 포폴을 준비하면서 시간이 있으니까 그 시간동안 부족한 점을 보완해서 조금 더 개발자에 걸맞는 사람이 되고싶습니다.
마지막으로, 한화시스템 Beyond SW캠프에서 배운 가장 큰 한 가지를 적으며 회고를 마치겠습니다.
이 문장을 가슴에 새기고, 앞으로 개발자로서의 삶을 열심히 한번 살아보겠습니다!