고급 프로젝트 종료 후 약 한 달이 지났다. (시간이 너무 빨라요⏰)
이제 더는 미룰 수 없다는 생각으로 회고를 작성해보려 한다..!
기간이 제일 길었던 만큼 새로운 기술 적용도 많이 하고 열정적으로 임했던 프로젝트였다.
옷장을 부탁해(Otboo)
실시간 날씨와 사용자의 옷장을 기반으로 오늘 입을 옷을 추천해주는 개인화 코디 추천 서비스입니다.
사용자가 보유한 의상을 체계적으로 관리하고, OOTD 피드·팔로우·DM 등 소셜 기능을 통해 코디를 공유할 수 있습니다.
⚙️ Backend Stack
📦 Framework
├── Spring Boot 3.5.5 # 메인 애플리케이션 프레임워크
├── Spring Data JPA # ORM 및 데이터 접근
├── QueryDSL # 동적 쿼리 작성
├── Spring Batch # 대용량 배치 처리 및 스케줄링
├── Spring Security # 인증·인가 및 보안 설정
├── OAuth2 Client # 소셜 로그인(OAuth2) 연동
├── Spring WebSocket # 실시간 양방향 통신
├── Spring WebFlux # 비동기 HTTP 클라이언트
├── Spring Mail # 이메일 발송
└── Gradle # 빌드 및 의존성 관리 도구
🗄️ Database & Cache
├── PostgreSQL # 운영 환경 RDBMS
├── H2 Database # 개발/테스트용 In-memory DB
└── Redis # 캐싱 관리
🔎 Search & AI
├── Elasticsearch # 검색 엔진
└── Spring AI 1.0.3 # OpenAI 연동
📧 Messaging
├── Apache Kafka # 도메인 이벤트 스트리밍
└── Spring Kafka # Kafka 퍼블리셔·컨슈머 구현
💿 Storage & External APIs
├── AWS S3 2.31.7 # 파일 스토리지
└── Jsoup 1.18.1 # HTML 파싱
📚 Documentation
├── Swagger/OpenAPI 3.0 # API 문서 자동화
└── Notion # 프로젝트 문서 및 협업 기록
└── Github Projects # 일정 및 이슈 관리
🔧 Development Tools
├── IntelliJ IDEA # 통합 개발 환경(IDE)
├── Git & GitHub # 버전 관리 및 협업
├── Discord # 팀 커뮤니케이션
└── Postman # API 테스트 도구
🚀 Infrastructure & Deployment
├── AWS # 클라우드 인프라
└── Docker # 컨테이너 기반 실행·배포
현상
weather_id가 null인 레코드가 들어오면서, 프론트에서 렌더링이 실패했다.원인
weather_id에 NOT NULL 제약을 걸지 않았다.ON DELETE SET NULL로 설정해, 날씨 데이터가 삭제되면 피드의 weather_id가 null로 바뀌도록 설계했다.해결
weather_id 컬럼에 NOT NULL 제약을 추가했다.ON DELETE RESTRICT로 변경해, 참조 중인 날씨 데이터는 삭제되지 않도록 보호했다.배운 점
조회에 필수적인 연관관계는 애플리케이션이 아니라 DB 스키마에서 강하게 보장하는 것이 안전하다.
특히 ON DELETE SET NULL은 편리해 보이지만, 실제 비즈니스 요구사항과 맞지 않으면 오히려 장애의 원인이 될 수 있다.
updatedAt 미갱신현상
updated_at이 갱신되지 않았다.원인
해결
updated_at = CURRENT_TIMESTAMP를 쿼리에서 명시적으로 업데이트하도록 변경했다.배운 점
대량 업데이트가 필요해 벌크 UPDATE를 사용할 때는,
Auditing/엔티티 리스너가 동작하지 않는다는 점을 항상 염두에 두고 쿼리에서 필드를 갱신해야 한다.
현상
원인
Instant.now()로 생성했다.해결
Instant.parse("2025-01-01T00:00:00Z")처럼 고정 값으로 치환하여 결정적 테스트를 보장했다.배운 점
테스트는 항상 결정적(deterministic)이어야 한다.
현재 시간, 랜덤 값에 직접 의존하는 코드는 설계 단계에서부터 테스트 가능성을 함께 고려해야 한다.
현상
운영 환경에서만 피드 조회 API가 다음 에러와 함께 500을 반환했다.
ConversionException: Unable to convert value '1760968800000' to java.time.LocalDateTime for property 'forecastedAt'
원인
forecastedAt 필드 타입은 epoch_millis(Long)이었다.해결
@JsonFormat을 제거해 문자열 포맷 강제를 해제했다.@Profile을 제거해 모든 프로파일에서 공통으로 등록되도록 했다.배운 점
검색 인프라(ES)의 타입과 애플리케이션 도메인 타입(LocalDateTime 등) 사이의 매핑은 명시적으로 설계해야 한다.
특히 테스트/운영 프로파일에 따라 Bean 구성이 달라지지 않도록 관리하는 것이 중요하다.
현상
원인
_id를 DB feed_id가 아닌 자동 생성 ID로 사용했다.delete(id) 호출 시 다른 문서를 바라보게 됨.is_deleted = false 필터를 까먹고 넣지 않았다.해결
_id를 DB feed_id와 동일한 값으로 고정했다.is_deleted = true로 반영했다.is_deleted = false 필터를 강제했다.배운 점
RDB와 검색 인덱스를 함께 사용할 때는 ID 전략(PK와 _id)과 삭제 전략(soft/hard delete, 조회 필터)을 처음부터 일관성 있게 설계해야 한다.
현상
원인
analysis-nori 플러그인을 설치하려 했다.해결
배운 점
인프라 의존성이 있는 플러그인은 빌드 타임에 강제하는 것이 전체 시스템 안정성에 유리하다.
현상
해결
현상
해결
현상
해결
현상
해결
현상
원인
해결
👉 프로젝트 종료 후 피드 목록 조회 RDB 기반 변경, 피드 물리 삭제, 논리 삭제 복구 기능, DM 대화방 생성 등의 리팩토링을 완료했다!🤩
검색/탐색 품질 고도화
추천 품질 향상
인증 신뢰성 확보 (OAuth2)
UI 일관성
테스트 품질 향상
프론트엔드까지 포함한 문제 해결 경험
일정 가시화 & 리스크 관리
커뮤니케이션 & 협업 방식
새로운 기술은 필요할 때 도입하자
피드 검색 기능에 Elasticsearch를 도입해 보면서, 현재 사용량 기준에서는 과설계에 가까울 수 있다는 점을 체감했다.
새로운 기술을 도입할 때는 “왜 필요한지”, “우리 규모에 맞는지”를 먼저 검토하고 도입 범위를 정해야 한다는 교훈을 얻었다.
LLM은 만능이 아니라, 적절하게 써야 한다
추천 로직 전체를 LLM으로 처리했을 때는 평균 응답 시간이 4초 이상으로 느려져 실서비스에는 적합하지 않았다.
결국 추천 산출은 자체 알고리즘, LLM은 추천 이유 생성으로 역할을 분리해 성능과 사용자 경험을 모두 만족시킬 수 있었다.
이 경험을 통해, 무작정 LLM을 도입하기보다 성능·비용·사용 시점을 함께 고려해 역할을 설계하는 것이 중요하다는 것을 배웠다.
일정은 “가시화 + 공유”로 관리해야 한다
스프린트 보드와 캘린더로 일정을 가시화하고, 팀 내에서 진척 상황을 꾸준히 공유하자 지연이 발생했을 때도 작업 재분배를 훨씬 빠르고 효율적으로 할 수 있었다.
팀원의 일정 변경이나 지연 가능성이 보이면 즉시 공유하고, 데드라인을 재협의하거나 작업을 재배정하는 방식이 프로젝트 리스크를 줄이는 데 큰 도움이 된다는 점을 배웠다.
백엔드에만 머무르지 않고 프론트까지 챙길 때 완성도가 올라간다
백엔드 기능 개발뿐만 아니라, 프론트엔드(UI·화면 흐름)까지 함께 다루면서 버그 대응 속도와 전체 서비스 완성도가 확실히 올라간다는 걸 느꼈다.
한쪽 영역에만 머무르기보다, 필요할 때는 프론트도 직접 보고 고칠 수 있는 역량이 협업과 운영에 큰 힘이 된다는 점을 깨달았다.
“내 도메인”을 넘어서 전체 코드베이스를 이해하려는 태도가 필요하다
다른 팀원의 도메인과 코드를 평소에도 함께 파악해 두어야, 작업 재배정이 필요할 때 맥락을 빠르게 이해하고 투입될 수 있음을 느꼈다.
협업 환경에서는 개인 담당 영역만 파는 것에서 끝나는 것이 아니라, 서비스 전체 구조와 주요 흐름을 함께 이해하려는 자세가 중요하다는 걸 느꼈다.

이번 프로젝트에서는 Spring Security, Redis, Kafka, OAuth2, WebSocket, Elasticsearch 등 새로운 기술을 많이 도입해 볼 수 있었다.
무엇보다도 이번에도 너무 좋은 팀원들을 만나 재밌게 프로젝트를 진행할 수 있었다.🙌
각자 맡은 영역에서 열심히 하는 모습을 보며 나도 많이 자극을 받았고,
의사소통 방식이나 협업 태도에서도 배울 점이 정말 많았다.
이번 프로젝트를 시작하면서 “팀원들의 코드를 꼼꼼히 리뷰해 보자”는 나만의 목표를 세웠는데,
꽤 성실하게 지켜낸 것 같아 개인적으로는 만족스럽다.
(적어도 내 역량 내에서는 최선을 다했다고 자부한다..! 👀)
물론 아쉬운 부분들도 꽤나 있었다.
우선 PM 역할을 처음 맡아보면서 일정을 더 적극적으로 관리하지 못한 점이 아쉽다.
초반부터 팀원들의 일정까지 함께 고려해 전체 스케줄을 설계했어야 하는데,
“내 일만 제때 끝나면 되겠지” 하는 안일한 마음으로 시작했던 것 같다.😵💫
일정이 조금씩 밀리기 시작했을 때도,
“1~2일 정도 밀리는 건 괜찮지 않을까?”라는 생각에 일정 조정을 대부분 팀원들에게 맡겨 두었다.
그때 강사님께서 데드라인을 팀과 함께 분명히 정하고, 지키기 어려운 경우에는 작업을 재분배하는 방식을 추천해주셨고, 이를 반영하면서 일정을 조금 더 타이트하게 관리할 수 있었다.
결과적으로는 작업을 재분배한 덕분에 일정 안에 마무리할 수 있었고, PM의 중요성을 다시 한 번 느끼게 되었다.
앞으로는 의견을 피력하거나 일정 관련해서 조율·재촉해야 할 때, 좀 더 분명하게 어필하고 적극적인 자세로 임해야겠다고 느꼈다.
의사소통 측면에서도 아쉬움이 남는다.
회의 시간에 수동적으로 따르는 태도를 보였던 순간들이 많았는데,
다음에는 회의 전에 미리 생각을 정리해 두고, 내 의견을 명확히 정리해 조금 더 주도적으로 참여해 보고 싶다.
개인적으로 이번 프로젝트에서 가장 좋았던 점은,
“프로젝트 끝났으니까 여기까지!”가 아니라 이후 리팩토링까지 함께 이어갔다는 것이다.
기간이 끝난 뒤에도 팀원들끼리 코어 타임을 정해서 리팩토링과 기능 보완을 이어갔고,
실제로 서비스를 계속 다듬어 가는 경험을 할 수 있었다.
이번 프로젝트는 나에게 꽤 의미 있는 프로젝트로 남을 것 같다.👍
실은 정말 회고 쓰기 귀찮았는데 막상 쓰고 나니 정리도 되고, 추억도 새록새록 떠올라서 뿌듯하다. 이제 진짜 취준하러 가야지...💨
트러블 슈팅이 정말 꼼꼼하게 작성되어서 스스로 생각하시면서 개발하신 것이 눈에 보이네요 본받고 싶어요!! 고급 프로젝트 고생하셨습니다 글 잘 읽고 갑니당 ^_^