[2주차] 팀원과 함께 고민한 기술 스택

코헤·2025년 12월 30일

cohiChat

목록 보기
2/10

2주차는 진짜 엄청나게 빡셌다..
진짜 빡셨다

커피챗 프로젝트를 본격적으로 시작하기 전에, 크게 세 가지로 나눠서 정리해볼 수 있을 것 같다.

  1. 팀원에게 모든 것을 일임한 것
  2. 같이 논의한 것 👈 이번 편은 여기까지!
  3. 내가 다 한 것들 ← 2편에서 계속
  4. 다음주에 할 것들

이번 글에서는 2번, 팀원이랑 매일 밤 10시까지 카톡을 붙잡고 논의했던 기술 스택 선정 과정을 정리해보려고 한다.


1. 팀원에게 모든 것을 일임한 것들

이 부분은 아예 팀원한테 맡겼다. 왜냐면 이런 건 팀원이랑 하나하나 논의하면서 세팅값 찾는 게 더 좋다고 생각했기 때문.

✅ 코드 품질 관리

ESLint + Prettier (프론트엔드), Checkstyle (백엔드)로 관리하기로 했다.

처음부터 에어비앤비처럼 빡빡한 규칙 대신, 널널하게 시작하기로 했다. 일단 개발 시작해보면서 불편한 부분 생기면 그때그때 룰 추가하는 게 낫더라.


그러면서 알게 된 것들

코드 컨벤션을 정하면서 "왜 이렇게 해야 하는지" 고민한 흔적들을 남겨둔다.

1️⃣ REST API URL은 kebab-case

결정: /user-profile, /order-items 이런 식으로

근거:

  • URL은 대소문자 구분이 애매한 환경도 있음 (Windows 서버 같은 거)
  • camelCase 쓰면 /userProfile vs /UserProfile 혼동 가능
  • REST API 업계 표준이 kebab-case에 가까움
  • 가독성도 더 좋음
    => 근데 케밥이다보니까 맛있어보인다

2️⃣ 들여쓰기는 Tab, Space 금지

결정: 들여쓰기는 무조건 Tab(\t) 사용

왜 Space 안 쓰냐면...

Space는 ' '를 여러 번 반복하는 거잖아? 근데 Tab은 단일 문자 \t 하나다.

과거 환경이나 일부 시스템에서 Tab과 Space를 서로 다른 문자로 인식해서, 혼용하면 diff 볼 때나 정렬할 때 난리남. Python 해봤으면 알 거임ㅋㅋ

Tab 쓰면 좋은 점:

  • 들여쓰기 의미가 명확함
  • 에디터 설정으로 표시 너비 조절 가능 (누군 4칸, 누군 2칸 선호해도 OK)
  • 논리적 들여쓰기와 시각적 표현을 분리할 수 있음

핵심: 들여쓰기의 "의미"는 Tab으로, 보여지는 "폭"은 에디터 설정에 맡긴다.
-> 하지만 우리는 단 둘이라서 별 신경 안쓰기로 했지롱 하하!!!!!!


3️⃣ import는 명시적으로, 와일드카드 금지

이런 거 쓰지 마세요

import java.util.*;

이렇게 쓰세요

import java.util.List;
import java.util.Map;

근거:

  • 와일드카드 쓰면 실제로 뭘 쓰는지 코드에 안 보임
  • 코드 리뷰할 때 의존성 파악 어려움
  • 일부 컴파일 환경에서 와일드카드 import는 컴파일 타임이 더 소요됨 => 알아보니까 컴파일 시간이 미미하다더라
  • IDE 자동 정리 기능(Optimize Imports)과도 궁합 안 좋음

💡 우리 컨벤션의 철학

"가독성보다 의도 명확성을 우선한다"

  • 에디터나 환경 차이로 깨질 수 있는 요소는 최대한 배제
  • 코드 리뷰할 때 "왜 이렇게 작성했는지"가 바로 보이도록 구성
  • 나중에 팀원이 늘어나도 혼란 없게

처음엔 이것저것 정하는 게 귀찮았는데, 막상 정해두니까 PR 날릴 때 쓸데없는 논쟁이 줄어들더라. 역시 선제적 합의가 답이다.

관련으로 재밌는 이슈가 있어서 공유한다
Change useTabs to true by default (Prettier #7475)

Prettier 2.0 버전에서 여러 기본 옵션을 변경하면서 useTabs 옵션의 기본값도 false에서 true로 변경하자는 제안이다.

✅ 개발 방법론: TDD를 도입하게 된 이유

"TDD를 진행해봅시다!" 라고 제안했다.

왜 TDD를 고민하게 됐냐면...

사실 우리 프로젝트는 FastAPI에서 Java/Spring으로 마이그레이션하는 게 핵심이잖아?

기존 Python 코드를 Java로 옮기면서 "이 로직이 제대로 동작하는지" 계속 확인해야 하는데, 매번 서버 띄워서 Postman으로 찔러보기엔 너무 비효율적이다.

그리고 마이그레이션이라는 게... 기존 기능을 그대로 옮기는 거잖아? "이전이랑 똑같이 동작하는가?"를 검증하는 게 제일 중요한데, 테스트 코드 없이 어떻게 확신할 수 있겠어?

그래서 TDD가 딱 맞다고 생각했다:
1. 기존 Python API 스펙 보고 → 테스트 케이스 먼저 작성
2. Java로 구현
3. 테스트 통과하면 → 마이그레이션 성공

이렇게 하면 회귀 테스트(Regression Test)도 자동으로 되고, 나중에 리팩토링할 때도 "내가 뭘 망가뜨렸나?" 걱정 안 해도 되니까.

근데 나도 TDD를 제대로 해본 적이 없어서, 팀원이랑 같이 공부하면서 적용해보자는 마음으로 인프런 강의에서 키워드를 얻어봤다

📚 추천한 강의: "더 자바, 애플리케이션을 테스트하는 다양한 방법" (총 5시간 16분)

섹션 1. JUnit 5 (2시간 13분)
섹션 2. Mockito (1시간 4분)
섹션 3. 도커와 테스트 (54분)
섹션 4. 성능 테스트 (33분)
섹션 5. 운영 이슈 테스트 (27분)
섹션 6. 아키텍처 테스트 (39분)

일단 JUnit이랑 모키토를 나도 공부해봐야겠다


2. 같이 고민한 것들

자, 여기서부터가 진짜 핵심이다. Spring Boot 버전이랑 Java 버전 선택하는 게 생각보다 고민이 많았다.

🔥 Spring Boot: 4.0 vs 3.5

처음엔 나도 Spring Boot 4.0이 솔직히 좀 당겼다.

4.0의 매력

  • 갓 나온 지 한 달밖에 안 된 따끈따끈한 버전
  • 에러 이슈 찾아서 해결하고, 블로그에 정리하면 선두주자가 될 수 있음
  • Spring Boot 컨트리뷰터 도전해볼 수도 있고
  • 기술 삽질하기 딱 좋음

근데 현실은...

  • DAU 10명 예상되는 프로젝트에 이게 필요한가? (허공 응시)
  • 에러 많이 안 고쳐진 프레임워크랑 잘 지낼 자신 있는가?
  • 일정 준수할 수 있을까?

결국 팀원이랑 논의 끝에 Spring Boot 3.5.9로 결정했다.

최종 결정 근거

  • ✅ 안정적인 LTS 버전
  • ✅ 레퍼런스 충분함
  • ✅ 일정 밀리지 않고 진도 나갈 수 있다고 확답 가능
  • ✅ 마이그레이션 프로젝트라는 목적에 충분히 적합
📌 Spring Boot 4.0.1 주요 변경사항 (접기/펼치기)

4.0이 궁금했던 사람을 위해 간단정리

주요 변경사항

  • Hibernate 7.2.0 업그레이드 (JPA 호환성 확인 필요)
  • Kotlin Serialization Starter 이름 변경
  • TestRestTemplate 사용 시 spring-boot-restclient 의존성 필수

버그 수정 (실무 관련)

  • WAR 배포 시 Spring Session 자동 설정 실패 문제
  • TestRestTemplate.getRootUri() 빈 문자열 반환 문제
  • Redis, RabbitMQ 헬스체크 에러

의존성 업그레이드

  • Spring Framework 7.0.2
  • Spring Security 7.0.2
  • Micrometer 1.16.1

더 자세한 내용은 공식 릴리스 노트 참고!


☕ Java: 21 vs 25

Java 버전도 고민이 많았다.
왜 여기에 17이 없냐고 묻는다면 귀찮아서 논의하지 않았다...

Java 21 (LTS)

  • 안정적이고 레퍼런스 많음
  • OpenJDK 도커 이미지도 많음
  • Lombok, Google Calendar API, IntelliJ 모두 호환 확인됨

Java 25 (최신)

  • Virtual Thread 개선됨
  • 근데 LTS 아님
  • Spring Boot 3.5.5부터 지원 가능
  • 너무 최신이라 제대로 테스트 안 된 느낌

우리 서비스 예상 트래픽이... DAU 10명 내외라서 (허공 응시2) Virtual Thread까지 필요한가 싶었다. 채팅 기능 같은 거라도 있으면 모를까.

그리고 Java 21의 Virtual Thread에 핀잉(pinning) 문제가 있어서, 가상 스레드 쓰려면 25가 낫다는데... 우리 프로젝트엔 과한 것 같았다.

최종 결정: Java 21 + Spring Boot 3.5.9

  • ✅ LTS 버전으로 안정성 확보
  • ✅ 충분한 레퍼런스와 커뮤니티 지원
  • ✅ JVM 고민도 할 수 있음 (이자나, 오브젝트, 토끼책 등)
  • ✅ 일정 준수 가능

📦 프로젝트 관리: "모든 것을 Git으로"

Jira 대신 GitHub 생태계를 전부 활용하기로 했다.

  • GitHub Projects: 프로젝트 보드
  • GitHub Wiki: 문서화 (단, Public이어야 함 - Private는 유료)
  • GitHub Issues: 이슈 트래킹
  • 마일스톤: 회의록도 여기서 관리

🗂️ 레포지토리 구조: 모노레포 채택

챌린지: CI/CD에서 변경사항 감지 및 선택적 배포 구현

  • 예: 서버 코드만 변경 시 클라이언트 배포 방지

근거: 1인 프로젝트로 시작했지만, CI/CD 학습 관점에서는 오히려 더 어렵고 좋은 경험이 될 것 같았다.


3. 모노레포에서 서버 띄우기

자, 이제 실전이다. 모노레포 구조에서 Spring Boot 서버 어떻게 띄우는지 정리해본다.

이런 장문의 설명을 냅다 카톡으로만 봐주신 팀원에게 감사인사를...

1단계: JDK 설치

IntelliJ에서 File → Project Structure → SDK로 들어가면 Java 다운받을 수 있다.

여기서 OpenJDK 21을 다운받는다.

왜 OpenJDK?

지금은 일단 OpenJDK로 시작하고, 나중에 Docker 이미지 크기 비교해서 최적화할 때 GraalVM Community 버전으로 바꿀 예정이다.
사유 : docker 이미지 최적화를 할정도의 시간이 남을지도 의문이고 graalvm을 잘 다룰 수 있을지도 아직 확신이 안서 이런 결정을 하게 되었다


2단계: Run Configuration 설정

Run/Debug Configurations 편집해본 적 있는가?

모노레포라서 프로젝트 루트가 cohi-chat인데, Gradle 세팅은 backend에 있다. 그래서 IntelliJ가 "어? Gradle 세팅 어디있어?" 하고 잉잉거린다.

이때 "아~ 내 루트 디렉토리는 backend야~" 하고 알려줘야 한다.

settings.gradle에서 이렇게 설정한다:

rootProject.name = 'backend'

TMI: gradlew.bat이 뭐 하는 애냐면...

Gradle Wrapper 배치 파일이다. Tomcat의 start.bat 같은 거라고 생각하면 된다.

gradlew.bat 파일 구조

@rem 주석
@if "%DEBUG%"=="" @echo off  // debug 모드 설정

if "%OS%"=="Windows_NT" setlocal  // OS 확인

set DIRNAME=%~dp0  // 디렉토리 경로 설정

@rem JVM 옵션 설정
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

여기서 JVM 튜닝 가능하다! 힙 메모리 최대/최소값 조정하고 싶으면 여기 수정하면 됨.

왜 이런 파일이 필요한가?

Spring Boot가 Bean 등록을 자동으로 해주잖아? 그 자동으로 등록된 걸 어딘가에 잘 모아둬야 하고, Tomcat이 알아서 JAR을 배포해주면 좋겠잖아?

그게 gradle/wrapper/gradle-wrapper.jar이다.


3단계: 기타 설정 파일들

  • .gradle: Gradle 설정 파일
  • .idea: IntelliJ 설정 (Eclipse가 맨날 잘못 읽고 울어버림ㅋㅋ 그래서 gitignore하는 곳도 있는데 난 귀찮아서 안 함)
    -> 참고로 귀찮아서 미뤘다가 추후에 ci/cd 할 때 열심히 삭제 했다 너무 힘들었다...

마무리

2주차 1편은 여기까지!

다음 편에서는 "내가 다 한 것들"을 정리해볼 예정이다. 에러 수정, 배포 설정, 그리고... 혼자 밤새 삽질한 이야기들ㅎㅎ

profile
하이하이

0개의 댓글