Do You Coding?

[회고] SSAFY 1학기 최종 관통 프로젝트 회고 본문

Project/Retrospect

[회고] SSAFY 1학기 최종 관통 프로젝트 회고


1. 프로젝트 소개

TRIPFLIX: 넷플릭스 컨셉의 여행 코스 공유 플랫폼

Tripflix 메인 화면

 

1. 프로젝트 개요

  • 진행 기간: 2025.11.14 ~ 2025.12.26 (6주)
  • 주요 기술: Java, Spring Boot, MyBatis, Vue.js 3, Python (FastAPI), MySQL, AWS S3
  • 한 줄 정의: "여행을 영화처럼 시청하다"라는 컨셉 아래, RAG 기반 AI 추천 기능을 포함한 여행 코스 큐레이션 서비스
  • 나의 역할:
     - DB 아키텍처 설계 및 구축
     - Gamification(뱃지/경험치) 시스템 엔진 개발
     - 공공 API 연동 및 데이터 파이프라인 구축
     - 리뷰/댓글/시청 기록(Log) 도메인 로직 구현
  • 팀원 역할: JWT 인증, 코스 핵심 CRUD, AWS S3, AI 추천 등

2. 프로젝트 진행 과정

팀 구성은 2인이었고, 약 6주(11/14 ~ 12/26) 동안 총 169개의 커밋을 쌓았다. 나는 그 중 66개의 커밋을 담당했다.


1 ~ 3주차 (11/14 ~ 12/5) - DB 설계와 프로젝트 세팅

mySQL ERD 설계 구조

 

: 나는 처음에 DB 스키마 설계에 가장 많은 시간을 쏟았다.

dbsql.sql을 계속 수정해가면서 테이블 구조를 잡았는데, 이 시기에만 DB SQL 파일을 5번 이상 업데이트했다.

Members, Badges, Member_Badges, Regions, Places, Courses, Course_Details,

Course_Zzim, Course_Logs, Reviews, Review_Comments 총 10개 테이블을 설계했다.

특히 신경 쓴 부분은 Courses 테이블에 genre, difficulty, target_audience를 넣어서 넷플릭스식 필터링을 가능하게 한 것과,

Course_Zzim/Course_Logs에 복합키와 UNIQUE 제약을 걸어서 중복 찜/중복 기록을 방지한 것이다.

 

Review_Comments는 처음부터 있던 게 아니라, 12/16에 feat: dbsql added review_comments table로 추가했다.

코스 별로 리뷰도 있겠지만, 해당 리뷰에 대해서도 댓글(코멘트)이 필요하다고 생각했기 때문에 추가했다.

 

또한 이 시기에, README에 요구사항 정의서, 클래스 다이어그램, ER 다이어그램, 간트차트, 화면 설계서를 정리했다.

 

그리고 작업하면서 프로젝트 기본 골격을 잡는 데도 참여했다.

이때 Regions 테이블명 오류를 발견해서 FIX: DB table Regions으로 수정한 적이 있는데, 테이블명이 쿼리랑 달라 한참 헤맸다.



4주차 (12/7 ~ 12/10) - 관광공사 API, Place, Profile, Badge 시스템

마이페이지 프로필 화면, 경험치 / 시청 코스 / 등급 / 뱃지 등

 

: 한국관광공사 TourAPI 연동이 이 주의 첫 번째 큰 작업이었다.

TourApiServiceImpl에서 areaBasedList2 API를 호출해서 50,000건의 관광지 데이터를 한꺼번에 가져온 뒤

DB에 저장하는 로직을 구현했다.

 

RestTemplate으로 API를 호출하고, Jackson ObjectMapper로 JSON을 파싱해서 TourItemDto로 변환하는 과정이었는데,

처음에 API 키가 소스 코드에 하드코딩되어 있어서 바로 application.yml로 분리했다.

그리고 TourApiServiceImpl 생성자에서 @Value로 키를 주입받는 부분에서 에러가 나서 수정했다.

Place 도메인에서는 PlaceController에 select(단건 조회)와 selectAll(전체 조회) API를 추가했다.


Profile 도메인을 처음부터 내가 설계하고 구현했다.

ProfileController, ProfileDao, ProfileServiceImpl, Profile DTO, profile.xml 매퍼까지 전부 새로 만들었고,

프로필 조회 시 해당 멤버가 획득한 뱃지 목록도 함께 보여주도록 ProfileServiceImpl.getProfile()에서

selectBadges()를 추가로 호출하는 구조로 만들었다.

이때 매핑이 안 맞아서 바로 다음 커밋에서 수정했는데, MyBatis XML에서 resultMap 매핑 오류였다.

 

수많은 Badge 이미지 생성의 흔적..


Badge 시스템도 이 주에 처음 만들었다.

BadgeController, BadgeDao, BadgeServiceImpl, Badge DTO, badge.xml 매퍼를 한꺼번에 올렸다.

이게 내가 이 프로젝트에서 공을 많이 들인 부분이고, 뱃지 로직의 핵심은 BadgeServiceImpl인데,

리뷰/댓글/좋아요/코스 제작 각각에 대해 개수 기반으로 뱃지를 자동 수여하는 구조다.

 

활동을 통해 마이페이지의 뱃지 컬렉션이 채워짐.


awardBadgeIfNotExists() 메서드로 중복 수여를 방지했고, @Transactional을 걸어서 뱃지 수여의 원자성을 보장했다.

뱃지 이름도 직접 지었는데, "여정의 시작", "꼼꼼한 기록가", "걸어다니는 지도", "전설의 기록", "소통의 신",

"명예의 전당" 같은 이름들이 컨셉에 잘 맞아서 만족스러웠다.



5주차 (12/14 ~ 12/19) - 댓글, 시청 기록, 찜/리뷰 카운트

내 여행 코스에서 찜한 코스, 다녀온 코스 등을 확인


: 찜 카운트 리뷰 카운트 API를 먼저 구현했다.

코스별로 찜 수와 리뷰 수를 조회하는 API인데, 프론트에서 코스 카드에 찜/리뷰 수를 표시하는 데 필요한 기능이었다.


댓글(Comment) 시스템이 이 주의 가장 큰 작업이었다.

먼저 Review_Comments 테이블을 DB에 추가한 뒤, CommentController, CommentDao, CommentServiceImpl,

Comment DTO, comment.xml 매퍼를 한꺼번에 올렸다.

댓글 작성 시 경험치 지급과 뱃지 체크가 동시에 일어나도록 CommentServiceImpl.insert()에서

memberService.gainExp()와 badgeService.checkCommentBadges()를 호출하는 구조로 연결했다.

이 부분은 나중에 리뷰/코스 작성에도 동일한 패턴을 적용했는데, 활동 → 경험치 → 뱃지 체크라는 일관된 흐름을 만든 게 좋았다.

이후, 댓글 수정 기능과 불필요한 import 정리를 추가했다.

시청 기록(Course Log) 시스템도 이 주에 구현했다.

CourseLogDao, CourseLog DTO, CourseLogServiceImpl, log.xml 매퍼를 한꺼번에 만들었고,

넷플릭스의 "시청 중", "시청 완료" 컨셉을 그대로 가져와서 status를 WATCHING/COMPLETED로 관리했다. getMyFilmography()라는 메서드명도 프로젝트 컨셉에 맞게 지었다.

리뷰와 시청 기록을 연동시킨 게 핵심이다.

ReviewServiceImpl.insert()에서 리뷰를 작성하면 해당 코스의 시청 기록을 자동으로 COMPLETED로 변경하고

반대로 리뷰를 삭제하면 해당 코스의 마지막 리뷰였는지 확인한 뒤 WATCHING으로 되돌린다.

"리뷰를 작성했다 = 이 코스를 완주했다" 라는 비즈니스 규칙을 코드로 표현한 것이다.

DB 테이블명을 course_logs로 변경하는 수정도 했고, 조회 로직을 추가했다.

또한 Log DTO에서 타입 불일치 버그를 수정했다.


6주차 (12/21 ~ 12/24) - 리뷰 고도화, Badge 완성

리뷰 작성
리뷰 작성 확인

 

: primitive long → wrapper Long 리팩토링을 먼저 진행했다.

CourseController에서 long으로 받던 파라미터를 Long으로 변경해서 null 처리가 가능하도록 했다.

(long은 null 처리가 불가능한 자료형이었고, 그 이유로 Long으로 설정해서 가능케한 것이다.)


리뷰 도메인을 본격적으로 고도화했다.

리뷰 CRUD를 확장하고, 코스별 리뷰 조회를 구현했다.

코스 상세 페이지에서 해당 코스의 리뷰 목록을 보여주기 위해 CourseServiceImpl.select()에서

코스 조회 시 관광지 목록과 리뷰 목록을 함께 가져오도록 했다.

Review DTO에도 필드를 추가했다.

이후 12/23 에는 리뷰 시스템을 대폭 리팩토링했다.

review.xml 매퍼를 34줄 삭제하고 30줄 새로 작성할 정도로 대대적으로 수정했다.

코멘트 매퍼에서 rno를 rcno로 변경하는 작업도 했다. 리뷰 삭제 시 status 변경 로직도 이때 추가했는데,

물리 삭제가 아니라 상태를 변경하는 방식으로 전환하고, 시청 기록 연동 로직을 함께 넣었다.

12/24에는 Badge 시스템의 마지막 퍼즐을 맞췄다.

BadgeServiceImpl에 checkCourseBadges()를 추가하고, CourseServiceImpl.insert()에서

코스 등록 시 경험치 + 뱃지 체크가 자동으로 호출되도록 연결했다.

찜을 받았을 때도 코스 작성자에게 좋아요 뱃지를 체크하는 checkLikeBadges()가 호출되도록 했다.

이로써 리뷰 작성, 댓글 작성, 코스 등록, 좋아요 받기 4가지 활동 모두에 대해 뱃지 자동 부여가 완성됐다.


최종 발표 직전 (12/25 ~ 12/26) - 경험치 시스템, 관리자 API, 마무리


: 크리스마스 새벽 5시 42분부터 작업을 시작했다.

뱃지 수여 관리자 API를 만들어서 BadgeAdminController에 관리자가 수동으로 뱃지를 수여할 수 있는 기능을 추가했다.

 

경험치 증가 로직 완성


바로 이어서 경험치 시스템을 완성했다.

MemberAdminController에 전체 회원 경험치 일괄 재계산 API(/api/v1/admin/members/xp-setup)를 만들었다.

경험치 공식은 코스 x 1000 + 리뷰 x 200 + 댓글 x 50으로 설계했다.

전체 회원의 코스/리뷰/댓글 수를 카운트한 뒤 공식에 대입해서 DB에 업데이트하는 구조다. 처리 시간도 로그로 남기도록 했다.

MemberService에 gainExp() 메서드를 추가해서, 코스/리뷰/댓글 작성 시

실시간으로 경험치가 적립되는 구조도 완성했다.

CommentServiceImpl, CourseServiceImpl, ReviewServiceImpl 모두에서 활동 성공 시

memberService.gainExp()를 호출하도록 통일했다.

마지막으로 README에 클래스 다이어그램, ER 다이어그램, 간트차트, 화면 설계서 이미지를 포함해서 최종 정리했다.


4. 잘한 점

DB 설계를 초반에 탄탄하게 잡았다.

10개 테이블의 FK 관계, CASCADE 설정, 복합키, UNIQUE 제약 등을 처음부터 꼼꼼하게 설정해서

개발 중에 대규모 스키마 변경이 없었다.

Review_Comments 테이블만 중간에 추가했는데, 이것도 기존 구조에 자연스럽게 붙일 수 있었다.

활동 → 경험치 → 뱃지라는 일관된 Gamification 흐름을 만들었다.

코스 등록, 리뷰 작성, 댓글 작성, 좋아요 받기 4가지 활동 모두에 대해

insert 성공 → gainExp() → checkXxxBadges() 패턴을 통일했다.

이 패턴 덕에 새로운 활동 유형을 추가하더라도 같은 구조로 확장할 수 있다.

리뷰-시청 기록 연동 로직이 깔끔했다.

"리뷰를 작성하면 시청 완료, 리뷰를 전부 삭제하면 시청 중으로 복원"

이라는 비즈니스 규칙을 ReviewServiceImpl에서 트랜잭션으로 처리한 게 만족스럽다.

countByMnoAndCno()로 마지막 리뷰인지 확인하는 디테일도 챙겼다.

관광공사 API 연동 경험을 쌓았다.

외부 공공 API를 호출해서 대량 데이터(50,000건)를 파싱하고 DB에 저장하는 전체 파이프라인을 경험했다.

API 키 관리, 에러 코드 체크, JSON 파싱 등 실무에 가까운 연동 작업이었다.

프로필 도메인을 온전히 처음부터 끝까지 설계/구현했다.

Controller부터 DAO, Service, DTO, XML 매퍼까지

한 도메인을 처음부터 만들어본 경험이 설계 감각을 키우는 데 도움이 됐다.


5. 아쉬운 점


새벽 코딩이 잦았다.

커밋 시간을 돌아보면, 12/17 새벽 4:42(댓글 시스템), 12/19 새벽 1:11(Log 기능),

12/25 새벽 5:42~6:33(뱃지/경험치) 에 작업한 기록이 있다.

마감에 쫓기기보다 일정을 앞당겨서 작업했으면 코드 품질도 더 좋았을 것이다.

테스트 코드를 작성하지 않았다.

특히 Badge 시스템은 리뷰/댓글/좋아요/코스 4개 도메인과 연동되는 복잡한 로직인데,

자동화된 테스트가 없어서 수동으로 Swagger에서 하나하나 확인했다.

checkReviewBadges()가 리뷰 1개일 때, 5개일 때, 300개일 때 제대로 동작하는지 단위 테스트가 있었으면

훨씬 안심하고 개발할 수 있었을 것이다.

커밋 메시지 일관성이 부족했다.

"Update file dbsql.sql", "Update 6 files" 같은 모호한 메시지가 여러 개 있다.

DB 스키마를 자주 수정하다 보니 대충 적은 건데, 나중에 이력을 추적할 때 뭘 바꿨는지 알기 어렵다.

앞으로는 fix: Badges 테이블 INSERT 구문 bno 수정 같이 구체적으로 적어야겠다.

BadgeServiceImpl의 하드코딩된 뱃지 번호.

checkReviewBadges()에서 awardBadgeIfNotExists(mno, 1), awardBadgeIfNotExists(mno, 2) 같이

뱃지 번호를 매직 넘버로 쓰고 있다. 상수나 enum으로 관리했으면 가독성과 유지보수성이 더 좋았을 것이다.

에러 처리가 약하다.

관광공사 API 호출 실패 시 빈 리스트를 반환하고 끝나고, 뱃지 수여 실패 시 로그만 찍고 넘어간다.

프론트에 의미 있는 에러 메시지를 내려주는 커스텀 예외 체계를 만들었으면 UX가 더 좋았을 것이다.

경험치 일괄 정산 API의 성능.

현재는 전체 회원을 루프 돌면서 한 명씩 3번의 COUNT 쿼리 + 1번의 UPDATE를 날리는 구조인데,

회원 수가 많아지면 느려질 수 있다. 배치 처리나 집계 쿼리로 최적화할 여지가 있다.


6. 배운 점

MyBatis XML 매퍼 작성에 익숙해졌다.

badge.xml, comment.xml, log.xml, profile.xml, review.xml 등 여러 매퍼를 직접 작성하면서

resultMap, 동적 쿼리, FK JOIN 등을 실전으로 익혔다.

특히 리뷰 매퍼를 34줄 삭제하고 30줄 새로 쓰는 리팩토링을 하면서, 처음부터 잘 설계하는 것의 중요성을 체감했다.


도메인 간 연동 설계를 경험했다.

리뷰 → 시청 기록, 활동 → 경험치 → 뱃지 같은 도메인 간 연결고리를 설계하면서,

Service 계층에서 다른 Service/DAO를 어떻게 조합하는지 감을 잡았다.


@Transactional의 중요성을 체감했다.

리뷰 삭제 시 시청 기록 상태 변경, 댓글 작성 시 경험치 + 뱃지 처리 같은 복합 작업에서

트랜잭션이 없으면 중간에 실패했을 때 데이터 정합성이 깨질 수 있다는 걸 직접 느꼈다.


외부 API 연동의 실제를 경험했다.

API 키 관리, 대량 데이터 파싱, 에러 코드 처리 등 실무에서 반드시 마주칠 작업들을 미리 해봤다.

생성자 주입에서 @Value 어노테이션이 안 먹어서 당황했던 경험도 기억에 남는다.


2인 팀에서 역할을 명확히 나누는 게 중요하다.

나는 데이터/소셜/Gamification, 팀원은 인증/핵심 CRUD/인프라/AI를 맡으면서 충돌 없이 병렬로 진행할 수 있었다.

이슈 템플릿과 PR 템플릿이 "서로 뭘 하고 있는지" 파악하는 데 큰 도움이 됐다.


7. 다음 프로젝트에서는


1) 테스트 코드를 처음부터 작성하겠다.

2) 최소한 Service 계층의 핵심 비즈니스 로직(뱃지 수여, 경험치 계산 등)에는 단위 테스트를 반드시 붙이겠다.
3) 커밋 메시지 컨벤션을 프로젝트 시작 시 합의하고, 첫 커밋부터 지키겠다.
4) 매직 넘버를 사용하지 않겠다. 상수 클래스나 enum을 적극 활용하겠다.
5) 일정을 2주 단위 스프린트로 관리해서 새벽 코딩을 줄이겠다.
6) 에러 처리 전략을 초반에 설계하겠다. 커스텀 예외 클래스를 만들고, @ControllerAdvice로 전역 예외 처리를 세팅하겠다.

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

[회고] SSAFY 2학기 공통 프로젝트 회고  (0) 2026.02.23