BEYOND SW 캠프 15기 두 번째 프로젝트(백엔드) 회고

mijuckboon·2025년 4월 20일
post-thumbnail

한화시스템 BEYOND SW 캠프의 두 번째 프로젝트인 백엔드 프로젝트가 끝났다. LearningCrew 팀은 운동 모임 개설 및 예약 플랫폼 LinkUp의 백엔드를 설계하고 구현하였다. 주요 기능은 다음과 같다.

1. 주요 기능

  1. 회원 관리 및 인증: 프로필, 계정 복구, 사업자 인증 등
  2. 운동 모임 개설 및 참여: 조건 기반 모임 생성 및 승인 관리, 실시간 인원 확인 등
  3. 포인트 기반 결제: 선결제, 자동 1/N 정산, 환불 처리, 포인트 충전 및 마일리지 등
  4. 매너온도 시스템: 후기, 노쇼 이력 기반 신뢰 시각화, 베스트 플레이어 선정 등
  5. 장소 예약 연동: 사업자 장소 등록 및 외부 예약 일정 연동 등
  6. 커뮤니티 및 친구: 자유 게시판, 댓글, 좋아요, 신고, 친구 추가, 활동 이력 조회 등
  7. 실시간 알림: 모임 승인/취소, 친구 알림 등
  8. 관리자 기능: 회원 제재, 신고 처리, 후기 이의신청, 사업자 승인, 공지사항 관리 등

2. 기여한 점

1. 팀명 선정

본 부트캠프에서 진행되는 모든 프로젝트는 각각 팀명과 프로젝트명을 정하도록 되어있다. 필자는 함께 배우는 동료 Learning Crew와 함께 달리는 동료 Running Crew의 두 가지 의미를 포함하는 러닝크루를 제안하였다. 운동 플랫폼을 만든 팀이라는 점에서 잘 어울리기도 해서, 논의를 거쳐 팀명으로 최종 선정되었다.

또한, 필자가 제안한 코드 자동 채점 기능을 포함한 부트캠프 LMS라는 프로젝트 주제는 최종 채택되진 않았지만 유력한 후보로 거론되었다. 프로젝트의 시작에 있어 좋은 주제를 선정 하는 것이 절반 이상은 차지하므로, 유력한 후보로 여겨질만한 좋은 주제를 제안하는 것도 큰 부분이라고 생각한다.

2. 문서화

첫 번째 DB 프로젝트와 마찬가지로 WBS 자동화, 기존 프로젝트 링크 따기, 컨벤션 논의 등에 참고할 자료 및 문서 양식을 공유하였다. 이번 프로젝트에서 이 자료들을 적절히 수정하여 활용하였다.

용어 통일

개발 프로젝트를 진행할 때는 혼란이 생기지 않도록 용어를 통일하는 것이 중요하다. 예를 들어 "회원"과 "사용자"는 지칭하는 대상이 다를 수 있고, "개설", "생성" 등 비슷한 뜻을 가진 용어를 혼용하게 되면 같은 뜻으로 사용되었는지 다른 뜻으로 사용되었는지가 명확하지 않다. 이를 명확히 하기 위해 이번 프로젝트에서는 용어 통일이라는 이름의 문서에서 이를 관리하였다.

노션 페이지 문서화

용어 통일 문서 자체는 다른 팀원분께서 만들어주셨고, DDD 설계나 요구사항 명세서 작성 등을 위한 논의에서 용어 통일에 해당하지 않는 용어가 등장하면 이를 지적하여 통일된 용어를 사용하도록 하는 역할을 수행하였다.
예를 들어 우리 서비스에서는 "포인트 반환"과 "포인트 환불"이 정해진 용어였는데, 다른 팀원이 "포인트 환급"이라는 용어를 사용하면 반환인지 환불인지 재차 물어보며 이를 맞춰갔다.

이 외에도 전 직장에서의 업무 경험 덕분에 다른 팀원들에 비해 디테일한 부분을 잘 보는 편이었다.

예시
2000원, 20002000

눈썰미가 좋은 독자라면 위에서 2000의 생김새가 서로 다르다는 것을 파악했을 텐데, 위의 예시 박스는 아래와 같이 작성되었다.

> 예시
> 2000원, $2000$원

당시 직장에서의 업무 규칙 상 맞춤법 등을 잘 지켜야 함은 물론이고, 왼쪽과 같이 달러 기호 없이 작성된 숫자는 모두 오른쪽과 같이 달러 기호 안에 작성해야 했다. 이런 업무를 수행하며 기른 눈썰미를 바탕으로 문서의 디테일한 부분을 잡아나갔다.
예를 들어 사용자로 작성했던 부분을 회원으로 변경하는 과정에서 Replace All 처리만 하면 회원가과 같이 잘못된 문법을 사용하게 되는데, 이런 부분이 발견되면 다시 회원가 -> 회원이으로 Replace All을 걸어 수정하였다.

주의: Replace All은 잘못 사용하면 변경하지 말아야할 부분까지 변경하는 경우가 있으므로 신중하게 사용해야 한다. 예를 들어 cat을 전부 dog로 Replace All 처리하면 category 또한 dogegory로 변경된다.
위의 케이스에서는 회원이가 잘못 쓰인 경우를 제외하면 회원가가 등장할 일이 없었기에 Replace All의 사용이 가능했다.

3. 모임 기능 구현

기능 구현 측면에서는 모임 개설, 모임 참가 신청, 모임 참가 신청 승인/거절, 모임 참가 취소 등 모임 관리에 관한 기능을 구현하였다.

4. merge 이후 compile error 발생 건 일부 수습

PR이 승인된 이후 main branch를 pull 받았을 때 compile error가 발생하는 경우가 종종 있었다. 이 중 한 건은 본인이 해결하여 배포하였는데, merge 순서가 꼬이는 바람에 ErrorCode Enum 클래스에 작성된 한 줄이 누락된 게 원인이었다.
IntelliJ 기능을 활용하여 해당 줄을 복구한 뒤 push하여 이 문제를 해결하였다.
IntelliJ Git

이 외에 PR 내용을 검토하는 사이에 이미 승인이 완료되었는데, 해당 PR에는 중괄호 짝이 맞지 않는 파일이 하나 있었다. 이 사실을 팀원들에게 전달하였고, 그 사이 다른 팀원분께서 수정본을 배포하셨다.

3. 트러블 슈팅

1. Duplicate Bean으로 인한 Exception 발생

본 프로젝트에서는 수업시간에 배운 구조에 따라 Repository interface를 만들고, infrastructure에 이를 extend하는 JPARepository를 작성하는 방식으로 repository가 구성되었다. 이는 JPA에 대한 의존성을 낮추어, JPA 대신 다른 기술로 교체할 때 유지보수를 편하게 하기 위함이다.
repository

이 구조로 코드를 작성하면 의존성 주입이 이루어질 때 같은 자료형으로 인식되는 두 repository 중 누구를 주입해야하는 지 인식하지 못해 Exception이 발생하는 경우가 종종 있었다. 이에 대한 Spring Boot 환경에서의 해결책은 아래와 같았다.

  1. 모든 repository 인터페이스에서 @Repository 어노테이션 삭제
    • 특히, JPA Repository가 아닌 일반 Repository에는 절대 붙이면 안된다.
  2. 위의 1을 적용하고도 동일한 문제가 발생하면 JPA Repository 쪽에 @Primary 어노테이션을 붙여준다.

2. 의존성 순환

모임 참가자 수에 따라 모임의 status를 변경하는 메서드를 작성한 후, 다음과 같은 에러 메시지가 뜨며 어플리케이션이 실행되지 않았다.
의존성 순환

의존성 주입이 일어나는 과정에서 세 클래스의 상호 의존성이 순환하는 바람에 발생한 문제였다. 처음 보는 문제 상황이어서, chatGPT의 도움을 받아 MeetingStatusService 클래스를 따로 분리하여 의존성이 순환하지 않도록 수정하여 해결하였다.

이 문제는 프로젝트 발표 당일에 수정하였는데, 다른 팀의 프로젝트 발표에서 트러블 슈팅 사례에 소개되어 반가웠다.

3. HTTP 500 Error 발생

장소 도메인에서는 장소ID가 필수 값이어서 null이 될 수 없지만, 모임을 개설할 때 사업자가 등록하지 않은 장소를 선택하는 경우 장소ID가 null이 될 수 있었다. (대신 장소에 대한 주소만 입력받음)
장소 관련 로직과 모임 관련 로직은 연관되는 부분이 있었기에 장소 담당자 분께서 모임 쪽 메서드를 수정하셨는데, 이 과정에서 null 처리가 제대로 이루어지지 않아서 테스트 시에 요청에 대한 응답으로 500 Error를 받았다. 이 문제는 메서드 작성 시 기본형인 int 대신 객체형 래퍼 클래스 Integer를 활용하여 null 값을 적절히 처리하는 로직을 추가함으로써 해결하였다.

예시 1

null 처리 예시 1

예시 2

null 처리 예시 2

이 사례를 통해 나의 기능이 아니더라도 서로 상대방의 기능을 잘 이해하고 있어야 하고, 잘못된 코드가 들어왔을 때 이를 수습할 수 있는 실력을 갖춰야 진정한 협업이 가능함을 알게 되었다.

4. 배운 점

1. CQRS(Command and Query Responsibility Segregation) 패턴

CQRS 패턴은 데이터의 조회를 담당하는 부분과 변경(삽입, 수정, 삭제)을 담당하는 부분을 분리한 패턴이다. 조회를 담당하는 부분은 MyBatis를 이용하고, 변경을 담당하는 부분은 JPA를 이용하였다. 이때, 모든 조회를 query에 맡기는 것이 아니라 데이터 변경에 필요한 조회는 command에서 처리하여, command와 query의 의존성을 낮추는 것이 핵심이었다.

첫 백엔드 프로젝트를 진행하느라 헤매는 와중, CQRS 패턴에 대해 잘못 이해하여 모든 조회를 MyBatis에 맡기는 바람에 프로젝트에서 맡은 부분의 기능을 구현하는 데 다소 문제가 발생하였다.

  1. JPA는 영속성 컨텍스트를 활용하지만 MyBatis는 실제 DB에서 데이터를 조회해온다.
    모임 테이블에 데이터를 삽입한 후, 그 데이터를 활용해야 하는 경우가 있었는데, JPA에서 save만 하고 flush를 하지 않았더니 영속성 컨텍스트에만 저장되고 실제 DB에는 저장되지 않기 때문에 해당 로직이 작동하지 않았다.
  2. JPA의 장점은 정합성 유지, MyBatis의 장점은 빠른 조회이다.
    위에서 언급한 문제를 극복하기 위해 flush를 하고 MyBatis에서 다시 조회해오는 방식으로 코드를 수정하였다. 그 결과 로직에는 문제가 없었지만, 알고보니 이는 MyBatis의 단점과 JPA의 단점을 모두 안고 가는 좋지 않은 방식이었다.

2. 코딩 컨벤션의 중요성

코딩 컨벤션이 중요한 이유로는 가독성, 코드 리뷰의 편의성 등만을 생각했는데, 이번에 프로젝트를 진행해보니 merge 과정에서 예상치 못한 현상들이 발생하였다.

git은 파일/코드에서 삽입, 수정, 삭제가 일어난 내역을 저장하여 병합할 때 한 줄에서 수정이 일어나면 conflict를 일으키지만, 새로운 줄이 추가된 것은 conflict 없이 자연스럽게 합친다. 이 과정에서 작성한 코드의 중괄호 짝이 맞지 않아서 compile error가 발생하는 경우가 종종 있었다.

즉, 같은 코드라도 어디에서 개행했는지에 따라 병합 과정에서 conflict가 발생하기도 하고 자연스럽게 추가되기도 한다. 둘 중 어떤 방식으로 병합하는 것이 좋은 것인지는 상황에 따라 다를 수 있다. 예를 들어, 메서드 중간에 추가 로직이 삽입되었는데 conflict가 발생하지 않는다면, PR에서 변경 사항을 확인하지 않은 경우 이 변화를 인지하지 못할 수 있다.

코딩 컨벤션을 합의하는 과정에서는 git의 예상 동작 또한 고려하여 협업이 원활하게 진행될 수 있는 방식으로 정해야 한다.

3. Git 관리의 중요성

사실 프로젝트에서 발생하는 문제의 70-80% 정도는 git과 관련된 문제였을 정도로 어려움이 많았다. PR이 승인되면 main branch를 pull 받아서 conflict를 해결한 뒤 자신의 PR을 올려야 하는데, 그 과정이 제대로 이루어지지 않아 merge에 어려움이 있었다. git 브랜치 전략을 잘 세우고 협업 방식을 잘 이해해야 원활하게 진행할 수 있음을 느꼈다.

4. 코드의 유지보수성

흔히 개발 완료 후에 유지보수를 잘 하기 위해 유지보수성 좋은 코드를 강조하는데, 개발 과정에서 또한 유지보수성 좋은 코드의 작성은 중요했다.
첫 프로젝트여서 정해진 기간 내에 구현을 완료하려다보니 유지보수하기 어려운 코드를 작성하게 되었는데, 그 결과 하나의 수정 사항이 발생할 때마다 수십줄을 변경해야 하는 등 어려움을 겪었다.

또한, 변수명을 생략하지 말라는 지침이 있음에도 너무 길어지다보니 줄여쓰곤 했는데, 이게 의존성 주입받는 클래스가 몇 개 없을 때는 괜찮았지만, 클래스가 많아지고 나니 관리가 어려웠다. 개발 흐름을 잘 몰라서 이렇게 많은 클래스를 주입받을 줄은 몰랐기에 발생한 시행착오였다. 유지보수성 좋은 코드를 작성하기 위한 지침들이 괜히 있는 게 아니었음을 몸소 체험하였다.

문자열을 직접 사용하는 것은 유지보수성이 나쁜 방식이어서 대안으로 Enum 클래스를 많이 사용한다고 하는데, 이 또한 문자열을 사용한 나쁜 코드를 작성하는 과정에서 몸소 느꼈다.

5. 프로젝트 구조

다들 CQRS 패턴으로 코드를 작성하는 게 처음이다보니, 작업 순서를 잘못 정하는 바람에 초반에 혼란이 있었다. Entity는 각각의 테이블마다 하나의 클래스로만 작성하는 것이 올바른 구조인데, 각 기능별로 만드는 바람에 문제가 발생했다. 다른 팀의 작업 방식을 보니 Entity부터 먼저 만들어놓고 시작했던데, 돌이켜보니 이게 올바른 구조였다. 만약 최종 프로젝트에서 CQRS 패턴을 채택하게 된다면 이 점을 상기해야겠다.

5. 느낀 점

  1. 프로젝트의 진행에 있어 소통은 정말 중요한 부분인데, 이번 프로젝트의 준비 초반에 소통의 부재로 인해 갈등이 발생하였다. 이로 인해 위기가 올 뻔했는데, 지금은 다행히도 잘 수습되어 무사히 프로젝트를 마무리하였다. 돌이켜봤을 때, 오히려 우리 팀이 더 단단해지는 과정이었고 프로젝트가 잘 진행되기 위한 한 번의 해프닝이었다고 생각한다.
    갈등을 피하려고 불편한 점이 생겨도 누군가가 참다보면 그게 쌓여서 결국 더 크게 터질 수 있다. 물론 갈등을 잘 수습하는 것이 쉽지 않지만, 갈등을 무작정 피하려고 하기보다는 그때그때 잘 봉합하며 대처하는 것이 중요하다는 것을 느꼈다.
  2. 전반적으로 첫 백엔드 프로젝트여서 흐름을 파악하는 데에 어려움이 컸고, 이로 인해 유지보수하기 어려운 코드를 작성하는 바람에 다소 아쉬운 결과물을 만들게 되었다. 유지보수성이 나쁜 코드임을 알면서도 기능을 완성은 해야하니, 작성을 멈추지 못하는 게 너무나 안타까웠다.
    이번 경험을 통해 배우고 느낀 점들을 바탕으로 최종 프로젝트에서는 처음부터 흐름과 구조를 잘 파악하여, 유지보수하기 좋은 코드를 작성하는 습관을 들여야겠다.
  3. 이번 프로젝트를 진행하며 현재 "수행 능력은 부족하지만 개발 흐름은 어느 정도 잘 파악하고 있는 상태"가 되었다고 생각한다. 첫 프로젝트였던 만큼 당장에 훌륭한 퍼포먼스를 보이기 보다는 흐름에 익숙해지는 것이 중요했다고 생각하며, 앞으로는 경험을 쌓으면서 수행 능력을 기르는 데 집중해보려 한다. 이번 경험을 통해 얻은 자신감을 바탕으로, 현재의 공부 과정을 즐기며 좋은 개발자가 되기 위한 길을 꾸준히 걸어보려 한다!
profile
곽진웅, 백엔드 개발자 준비 중

0개의 댓글