Project/Monew

[Monew] 개인 회고록 - 기술보다 어려웠던 것들

승주우에요 2026. 4. 27. 11:09

팀장을 맡으면서

이번 프로젝트는 초급 때와 결이 달랐다. 기능 구현 자체는 어렵지 않았지만 작업 순서, 초기 설정이 훨씬 힘들었다.

팀에서 기능 구현 후 테스트 코드를 작성하기로 합의했는데, 이 결정을 지키지 않는 팀원이 생기면서 균열이 시작됐다. 테스트 코드가 없으면 통합 테스트 일정이 밀리게 되고, 중간 작업 시간이 붕 뜨게 되면서 불만을 표출하셨다 ㅠㅠ 그 여파로 팀 분위기가 눈에 띄게 가라앉았다. 한 팀원은 이후 프로젝트 참여 자체에 소극적이 되어버렸다.

팀장으로서 이걸 직접 꺼내서 풀어야 할지, 아니면 내버려두고 프로젝트를 끝내야 할지 판단이 서지 않았다. 강사님은 "이걸 터뜨리고 갈지 말지를 팀장이 결정해야 한다" 고 하셨는데, 그 말이 생각보다 어렵게 다가왔었다 ㅠㅠ


기술적으로 아쉬웠던 것들

기능이 안 나오거나 하는 큰 문제는 없었다. 다만 세부적인 합의가 부족했던 것 같다!

  • 응답 DTO 타입을 팀원마다 다르게 정의해서 나중에 맞추는 비용이 생겼다
  • 도메인 간 의존성을 고려한 작업 순서를 사전에 조율하지 않았다.
  • 한 팀원의 공백 기간이 예상보다 컸고, 이에 관한 불만이 컸었다.
  • 세부적인 배포 전략 및 일정 조율도 더 일찍 했어야 했다.
  • 러프하게 라도 전체 프로젝트 일정을 짜야 했다. QA 대응 시간, 문서화 시간이 빡빡해서 마지막에 힘들었다 ㅠㅠ

프로젝트는 혼자 원하는 대로 흘러가지 않는다는 걸 다시 한번 실감했다. 기능 단위의 합의뿐 아니라 코드 컨벤션, 타입 통일, 작업 순서까지 초반에 맞춰두지 않으면 나중에 반드시 비용으로 돌아온다.


AI를 도구로 쓰는 법 — 강사님께 배운 것

역할을 나눠서 쓴다

강사님이 직접 쓰시는 방식이 인상적이었다.

단계 도구

구현 GPT
테스트 Claude
리뷰 다른 모델
토큰 부족 시 로컬 모델

모델 하나에 의존하지 않고, 각 모델의 강점에 맞게 역할을 분배하는 방식이다. 생산성 자체가 역량이 되는 시대라는 말이 와닿았다.

프로젝트 이후 해볼 것들

강사님이 과제로 남겨주신 것들:

  1. 내 코드 vs AI 코드 비교 — 프로젝트가 끝난 뒤 같은 기능을 Codex, Claude Code로 구현해보고 차이를 직접 분석
  2. QA 대응에 AI 활용 — Codex를 QA 단계에서 어떻게 활용할지 전략 세우기
  3. MD 문서화 — 성능 지표, 인프라 설계를 Markdown으로 정리

결국 방향은 AI Driven 개발자다. AI를 쓰냐 안 쓰냐가 아니라, 어떻게 잘 쓰냐가 앞으로의 경쟁력이 된다고 하신다!


✅ 좋은 점

도메인 설계가 탄탄했다

ERD 기준으로 soft delete 전략, polymorphic 알림 구조, M:N 중간 테이블 설계가 처음부터 잘 잡혀 있었다. article_views의 UNIQUE 제약으로 중복 조회를 DB 레벨에서 막은 것, notifications의 resource_type 기반 분기도 의도가 명확했다.

성능 개선을 실제로 적용했다

  • 댓글 목록 조회 시 불필요한 COUNT 쿼리 제거 (603a192)
  • 댓글/알림 인덱스 추가 (a464db2)
  • UserActivity 이벤트 리스너 비동기 처리로 HTTP 응답 지연 개선 (044bb0b)

이론에서 그치지 않고 실제 코드에 반영했다는 점이 좋았다.

테스트를 포기하지 않았다

총 751커밋 중 test 접두사 커밋만 74개. Repository/QueryDSL 단위 테스트, Controller 슬라이스 테스트, 통합 테스트까지 레이어별로 커버했다. TestContainers 도입으로 실 DB 환경 기반 테스트도 구성했다.

이벤트 기반 아키텍처를 직접 경험했다

@TransactionalEventListener, ApplicationEventPublisher, Outbox 패턴까지 손으로 짜보면서 이론이 아닌 실전 감각을 쌓았다.


📚 배운 점

@TransactionalEventListener의 트랜잭션 경계

AFTER_COMMIT 시점에는 JPA 영속성 컨텍스트가 이미 닫혀 있다. 이 때문에 알림 저장 로직에서 LazyInitializationException이 발생하거나, 트랜잭션이 없는 상태로 DB 접근을 시도하는 문제를 직접 겪었다. 이벤트 리스너에서 새 트랜잭션(@Transactional(propagation = REQUIRES_NEW))을 열거나, 필요한 데이터를 이벤트 객체에 미리 담아야 한다는 걸 몸으로 배웠다.

시간 타입은 통일이 먼저다

Instant ↔ LocalDateTime 변환 관련 커밋이 여러 번 반복됐다 (9aa5fb4, d111bc2 등). DB timezone 설정, JPA 매핑, 비교 연산 모두 타입이 하나로 통일되지 않으면 조용히 버그가 생긴다. 프로젝트 시작 시점에 "DB는 UTC, 애플리케이션은 LocalDateTime, 변환은 어디서 할 것인가"를 팀 규칙으로 못 박았어야 했다.

이벤트 단건/다건 발행의 의미 차이

InterestNotificationEvent를 다건 발행하다가 단건으로 전환한 이유(c47cfe4)는 단순한 리팩토링이 아니다. Listener가 리스트를 순회하면서 하나 실패했을 때 전체가 롤백되는 문제, 개별 알림 처리 실패의 격리 등이 얽혀 있었다. 이벤트 발행 단위를 결정할 때 "실패 격리"를 기준으로 생각해야 한다는 것을 배웠다.

커서 페이지네이션에는 타이브레이커가 필수다

createdAt 또는 likeCount + createdAt만으로 커서를 구성하면, 동일한 값이 페이지 경계에 걸릴 때 중복 조회나 누락이 발생한다. ORDER BY의 마지막 컬럼은 항상 UNIQUE한 id가 들어가야 하고, 커서 조건도 3단계 비교가 되어야 한다. 이번엔 DTO 변경 제약으로 보류했지만, 설계 단계에서 잡았어야 할 문제였다.

알림 수신자 로직은 이벤트 발생 주체와 혼동하기 쉽다

댓글 좋아요 알림의 수신자가 "좋아요를 누른 사람"으로 구현되어 있다가 "댓글 작성자"로 수정됐다 (0c417e0). 이벤트를 발행하는 주체(좋아요 누른 사람)와 알림을 받아야 하는 주체(댓글 작성자)를 혼동한 케이스. 비즈니스 규칙을 코드로 옮기기 전에 "누가 → 누구에게" 흐름을 명시적으로 써보는 습관이 필요하다.


😓 아쉬운 점

커서 타이브레이커는 기술 부채로 남았다

메모리에 기록해두었지만, 댓글 커서 페이지네이션의 id 타이브레이커 추가는 결국 미완으로 끝났다. DTO 변경 불가라는 현실적 제약이 있었지만, 처음 설계할 때 cursor 필드에 "값|uuid" 인코딩을 넣었다면 피할 수 있었다.

이벤트 발행 코드가 여러 번 뒤집혔다

단건/다건, Listener 구조 변경이 4커밋에 걸쳐 반복됐다. 처음부터 "단건 발행 + 실패 격리" 원칙을 잡았다면 줄일 수 있었던 시행착오였다.

시간 타입 정책이 늦게 통일됐다

프로젝트 중반에 Instant → LocalDateTime 전환이 일어나면서 테스트 코드까지 연쇄 수정이 발생했다. 초반에 팀 컨벤션으로 확정했어야 할 사항이었다.

분석과 구현 사이의 피드백 루프가 느렸다

버그를 분석해서 원인을 짚어도, 실제 코드 수정 → 테스트 통과 → 재검증까지 오래 걸리는 경우가 있었다. 분석 결과를 바로 코드에 반영할 수 있는 환경이었으면 더 빠르게 순환할 수 있었을 것이다.


🚀 추가로 해보고 싶은 것들

커서 페이지네이션 타이브레이커 완성

cursor = "값|uuid" 인코딩 방식으로 afterId 없이 기존 DTO를 유지하면서 타이브레이커를 추가할 수 있다. 이 방식으로 댓글과 알림 커서 쿼리를 완성하고 싶다.

Outbox 패턴 안정화

현재 UserActivity는 비동기 이벤트로 처리하지만, 이벤트 유실 가능성이 있다. Outbox 테이블에 이벤트를 먼저 저장하고 별도 스케줄러가 발행하는 구조로 바꾸면 유실 없이 처리할 수 있다. 트랜잭션 내 이벤트 저장 → 커밋 후 폴링 발행의 흐름을 직접 구현해보고 싶다.

알림 실시간 Push (SSE 또는 WebSocket)

현재는 폴링 방식(클라이언트가 주기적으로 미확인 알림 조회)이다. Server-Sent Events로 서버에서 알림 발생 시 실시간 Push하는 구조를 추가하면 UX가 크게 개선된다.

QueryDSL 쿼리 실행 계획 검토

인덱스를 추가했지만 실제로 EXPLAIN ANALYZE로 실행 계획을 확인한 적이 없다. 댓글 목록 조회, 알림 목록 조회 쿼리에 대해 실행 계획을 보고 인덱스가 제대로 타는지 검증하고 싶다.

통합 테스트 시나리오 확장

현재 통합 테스트는 단일 API 단위다. "사용자 생성 → 관심사 구독 → 기사 조회 → 댓글 작성 → 좋아요 → 알림 수신"처럼 전체 흐름을 하나의 시나리오로 검증하는 E2E 통합 테스트를 작성해보고 싶다.

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

[Monew] 복기 - 일정·개발 측면 회고  (0) 2026.05.10
[Monew] 개인 개발 리포트  (3) 2026.05.06