[프로젝트 회고] 7STARBALL Meeting API 초기 구현기

들어가며

7STARBALL 프로젝트에서 내가 처음 크게 맡은 기능은 Meeting 도메인이었다.

Meeting은 단순한 게시글이 아니라 사용자가 직접 모임을 만들고, 다른 사용자가 참여하고, 시간이 지나면 상태가 바뀌는 기능이었다. 그래서 CRUD만 구현하면 끝나는 기능은 아니었다.

초기 구현에서 목표는 명확했다.

1
프론트에서 모임 화면을 구성할 수 있을 만큼 API 흐름을 먼저 완성하자.

이 글은 Meeting API의 첫 구현을 정리한 기록이다.

구현한 API

초기 PR에서 구현한 API는 다음과 같았다.

  • GET /meetings: 전체 모임 목록 조회
  • GET /meetings/{id}: 단일 모임 상세 조회
  • GET /meetings/my: 로그인 유저의 모임 조회
  • GET /meetings/count: 유저별 모임 개수 조회
  • GET /meetings/{id}/members: 모임 참여자 목록 조회
  • POST /meetings/{id}/join: 모임 참여
  • DELETE /meetings/{id}/leave: 모임 참여 취소
  • POST /meetings: 모임 생성
  • PUT /meetings/{id}/update: 모임 수정

처음에는 API가 많아 보여서 부담이 컸다.
하지만 기능을 화면 기준으로 쪼개보니 필요한 흐름이 보였다.

모임 목록 화면, 상세 화면, 내 모임 화면, 참여 버튼, 생성/수정 화면이 각각 어떤 데이터를 필요로 하는지 생각하면서 API를 나눴다.

DTO와 VO 설계

Meeting API는 응답 구조가 단순하지 않았다.

목록에서는 간단한 장소 정보와 태그, 상태, 참여자 수가 필요했고, 상세에서는 더 많은 정보가 필요했다. 그래서 요청/응답 DTO와 VO를 나누었다.

대표적으로 다음 구조를 만들었다.

  • CreateMeetingRequest
  • UpdateMeetingRequest
  • MeetingListResponse
  • MeetingDetailResponse
  • MeetingDetailAndMemberResponse
  • ParticipantResponse
  • ListSpot
  • DetailSpot
  • Tag

처음에는 DTO가 많아지는 것이 복잡하게 느껴졌다.
하지만 목록 응답과 상세 응답을 하나로 합치면 오히려 사용하지 않는 필드가 많아지고, API 의도가 흐려질 수 있다고 생각했다.

그래서 화면에 맞춰 응답 DTO를 나누는 방향으로 정리했다.

Repository 확장

Meeting 기능에는 여러 Repository가 필요했다.

  • MeetingRepository
  • ParticipantRepository
  • TagRepository
  • MemberRepository
  • OutdoorSpotRepository

특히 모임 목록 조회에서는 커서 기반 조회가 필요했다.

1
createdAt desc, id desc

를 기준으로 정렬하고, 다음 페이지 요청에는 마지막으로 본 데이터의 ID를 넘겨받는 방식이었다.

처음에는 Pageable만 쓰면 되지 않을까 생각했지만, 무한 스크롤이나 대량 데이터 조회에서는 커서 방식이 더 적합하다고 판단했다.

Service 구현에서 어려웠던 점

가장 어려웠던 부분은 참여 로직이었다.

참여 로직은 다음 조건을 확인해야 했다.

  • 존재하는 모임인지
  • 존재하는 사용자 Foreign Key인지
  • 이미 참여한 사용자인지
  • 정원이 찼는지
  • 모임 상태가 참여 가능한 상태인지

처음 구현에서는 이런 검증 로직이 Service에 많이 들어갔다.
기능을 완성하는 데는 효과적이었지만, 나중에 코드가 커지면서 리팩토링이 필요하다는 생각이 들었다.

이 경험이 이후 Rich Domain 리팩토링으로 이어졌다.

Mapper를 둔 이유

Meeting 생성과 수정에서는 엔티티 변환 코드가 반복됐다.

그래서 MeetingMapper를 두고 요청 DTO를 엔티티로 바꾸거나, 상태/태그 수정에 필요한 변환 로직을 분리했다.

처음에는 Mapper가 꼭 필요한지 고민했다.
하지만 Service 안에서 변환 코드가 많아지면 핵심 비즈니스 흐름이 잘 보이지 않았다.

그래서 Service는 흐름을 담당하고, Mapper는 변환을 담당하도록 나누었다.

배운 점

초기 구현은 완벽한 설계를 만드는 시간이 아니었다.

일단 기능을 화면과 연결할 수 있게 만들고, 그 과정에서 도메인의 복잡도를 직접 파악하는 시간이었다.

Meeting API를 처음 구현하면서 배운 점은 다음과 같다.

  • API는 화면 흐름을 이해해야 잘 나눌 수 있다.
  • DTO는 DB 구조가 아니라 사용자의 응답 요구사항을 기준으로 설계해야 한다.
  • 참여/취소 같은 기능은 단순 CRUD보다 훨씬 많은 도메인 규칙을 가진다.
  • 처음부터 완벽한 구조보다, 동작하는 구조를 만들고 리팩토링 포인트를 발견하는 것도 중요하다.

마무리

Meeting API 초기 구현은 이후 작업의 기반이 되었다.

이때 만든 구조 위에서 테스트를 붙이고, 응답 구조를 정리하고, Rich Domain으로 리팩토링하고, N+1과 동시성 문제를 개선했다.

돌아보면 부족한 점도 많았지만, 이 초기 구현이 있었기 때문에 이후의 개선 방향도 보일 수 있었다.

Leave a comment