[회고] Book1lluwa 프로젝트 회고
2025.06 ~ 2025.07, 약 두달간의 MSA 프로젝트 여정
프로젝트를 돌아보며..
2달 전, 우리 8명이서 뭔가 대단한 걸 만들어보자고 으쌰으쌰 했던 게 엊그제 같은데, 벌써 프로젝트가 끝났다. 처음에는 그냥 "도서 쇼핑몰 하나 만들어보자" 로 시작했던게, 결국 MSA 아키텍처에 CI/CD 까지 구축한 제법 그럴듯한 서비스가 탄생했다. 8명이라는 비교적 다수의 팀 프로젝트의 리더를 맡으며 힘들었던 부분도 많지만, 그 덕분에 스스로나 팀 적으로도 많이 성장할 수 있었던 값진 경험이었다.
가장 기억에 남는 순간들
팀 리더를 맡으며, 대부분의 고민들을 함께하고 신경쓰지 않은 부분들이 없는 것 같다. 그 중에서도 내가 맡은 부분들에 대해서 적어보자면,
1. MSA 설계 단계 - 머리 아픈 날들
생각해보면 처음부터 끝까지 머리 안아픈 날들이 없었다. 그 중에서도 첫 시작인 도메인을 어떻게 나눌지 부터가 고민이었다. 8명이서 각자 다른 의견을 내면서, "회원이랑 인증을 분리해야 하나?", "주문이랑 결제는 어디까지 묶이지?" 정답은 없지만 시작이니 만큼 확실한 기준을 세우고 도메인을 나눠야 했다.
최종적으로 우리는 8개의 서비스로 분리했다. (Auth, Member, Book, Order, Payment, Coupon, Review, Front) 각자 어떤 도메인을 맡을지 정하기도 어려웠지만, 지금 돌이켜보니 이때의 고민이 나중에 개발할 때 도움이 많이 됐다.
2. Gateway에서 JWT 검증 구현 - 삽질의 연속
이게 생각보다 오래 걸렸다. MSA 방식으로 처음 구현하다보니, Gateway 어디까지 책임을 맡아야하는지가 애매했다. 우리는 Front 서버를 따로 두고, 내부 API 요청은 Gateway가 단일 진입점 역할을 하도록 설계 했는데, jwt 검증이나 refresh 토큰 발급 로직을 Gateway에 두는게 맞는가? 라는 생각이 컸다.
// Gateway에서 JWT 검증 후 사용자 정보를 헤더로 전달
if (jwtUtil.validateToken(token)) {
String userId = jwtUtil.extractUserId(token);
String userRole = jwtUtil.extractUserRole(token);
exchange.getRequest().mutate()
.header("X-User-Id", userId)
.header("X-User-Role", userRole);
}
모든 API 요청의 진입점이면서, 검증 및 헤더 추가 역할까지 Gateway가 하게 되면 얘가 하는 역할이 너무 많아지지 않을까 걱정했지만, 결국 다운스트림 서비스에 전달하기 위해선, 이 방법이 주는 이점이 더 많아서 잘한 선택이었다고 생각한다.
3. 무중단 배포 구현 - 새벽 3시의 기적
CI/CD 파이프라인을 구축하면서 가장 뿌듯했던 순간이다. 각 서비스를 두 개의 인스턴스로 띄우고, 배포할 때 하나씩 교체하는 롤링 배포 방식을 구현했다.
# === 설정 ===
SERVICE_NAME=auth-service
JAR_NAME=1lluwa-auth-service.jar
DEPLOY_DIR=~/deploy/auth
LOG_DIR=$DEPLOY_DIR/logs
PROFILE=prod
PORTS=(10303 10304)
# === 새 인스턴스 실행 ===
mkdir -p "$LOG_DIR"
for PORT in "${PORTS[@]}"; do
PID=$(lsof -ti :$PORT)
if [ -n "$PID" ]; then
kill $PID
sleep 2
else
echo "포트 $PORT 에 실행 중인 프로세스가 없습니다."
fi
nohup /usr/local/java/java21/bin/java \
-jar $DEPLOY_DIR/target/$JAR_NAME \
--spring.profiles.active=$PROFILE \
--server.port=$PORT \
> $LOG_DIR/$SERVICE_NAME-$PORT.log 2>&1 &
# 포트 열림 확인
for i in {1..10}; do
if lsof -i :$PORT > /dev/null; then
break
fi
sleep 2
done
# 헬스 체크
for i in {1..10}; do
STATUS=$(curl -s http://localhost:$PORT/actuator/health | grep -o '"status":"[^"]*"' | head -n 1 | awk -F: '{print $2}' | tr -d '"')
if [ $STATUS = "UP" ]; then
break
fi
sleep 5
done
if [ $STATUS != "UP" ]; then
exit 1
fi
done
새벽에 서버 띄워놓고 배포 테스트하면서 헬스 체크에 성공하는 걸 보니까 정말 뿌듯했다. 물론 완벽한 무중단 배포 방식은 아니어서 조금 더 개선해나가야 겠지만, 이전 보다는 더 자연스럽게 새 버전으로 교체되는 모습을 보니 괜히 감격스러웠다.
기술적 도전과 성장
Redis 캐싱 전략
알라딘 API 에서 베스트셀러 정보를 가져올 때, 매번 외부 API를 호출하면 응답 속도도 느리고 API 호출 제한에도 걸릴 수 있어서 Redis 캐싱을 도입했다. 처음에는 단순히 데이터만 저장했는데, TTL 설정과 캐시 무효화 전략까지 고려하다 보니 캐싱이 생각보다 복잡한 영역이라는 걸 깨달았다.
Elasticsearch 검색 엔진 구축
도서 도메인을 담당하신 분이 구현한 검색 기능인데, 우리 팀은 매주 금요일 주간 스크럼 겸 기술 세미나를 진행하여 각자 도메인에 적용한 기술을 팀원들에게 발표하는 시간을 가졌다. 단순 LIKE 검색에서 Elasticsearch 기반 전문 검색으로 바뀌니까 검색 품질이 확연히 달라졌다. 가중치를 적용하여 관련도 높은 결과부터 보여주는 것도 인상적이었다.
RabbitMQ 메시지 큐 도입
이건 쿠폰 쪽 구현을 맡으신 분이 쿠폰 자동 발급 기능을 구현하면서 RabbitMQ를 도입하셨는데, 이것도 정말 좋은 경험이었다. 회원가입 이벤트가 발생하면 비동기로 쿠폰을 발급하는 구조인데, 시스템 간 결합도를 낮추면서 확장성도 높일 수 있어 MSA 의 장점을 제대로 활용한 사례라고 생각한다.
팀워크와 협업
팀원분들도 협업 경험이 별로 없으신 분들이 대부분이라 이 부분이 가장 걱정이었는데, 다행히도 팀 컨벤션을 잘 따라와주셔서 그나마 수월하게 협업이 가능했다.
- [라벨]/[이슈번호]-[작업명] 브랜치 네이밍
- 커밋 메시지에 이슈번호 필수 포함
- SonarQube 품질 체크 통과 후 머지
혹여나 다른 분들이 번거롭게 느끼지 않을까 걱정이 많았지만, 다들 코드 품질도 올라가고 작업도 수월해진 모습에 별 말 없이 잘 따라주셔서 감사할 따름이다.😄
매일 아침 스크럼 회의를 하면서 각자 진행 상황을 공유했는데, 이게 정말 중요했다. 누가 어디서 막혀 있는지, 어떤 부분에서 도움이 필요한지 바로바로 알 수 있어서 문제를 빨리 해결할 수 있었다. 또한 위에서 말한 매주 금요일 세미나 역시, 다른 도메인을 이해하는데 도움을 많이 줘서 좋았다.
아쉬웠던 점들
초기 설계의 아쉬움
서비스 간 의존성을 완전히 제거하지 못한 부분들이 있었다. 특히 주문 서비스에서 회원 정보를 가져올 때 직접 호출하는 부분들이 있어서, 완전한 MSA 보다는 분산 모놀리식에 가까운 면도 있었다. 다음에는 이벤트 기반 아키텍처를 더 적극적으로 활용해보고 싶다.
테스트 커버리지
마감 기한을 정해두고 진행한 프로젝트이다 보니, 기능 구현에 집중하여 테스트 코드 작성이 후순위로 밀렸다. SonarQube로 커버리지를 체크하긴 했지만, 더 체계적인 테스트 전략이 필요했다고 생각한다.
모니터링과 로깅
서비스가 많다 보니 장애 발생 시 어디서 문제가 생겼는지 추적하기 어려웠다. ELK 스택이나 분산 트레이싱 도구를 도입했으면 개발 단계에서 더 수월하게 작업이 가능했을 것 같다.
개인적인 성장
아키텍처에 대한 이해
이전에는 단일 애플리케이션이나 모놀리식 프로젝트만 경험해봤는데, 이번 프로젝트를 통해 분산 시스템의 복잡성과 장단점을 몸소 체험했다. 서비스 간 통신, 데이터 일관성, 장애 전파 등 MSA 환경에서 고려해야할 것들이 정말 많다는 걸 알게 됐다.
DevOps, 어쩌면 적성에 맞을지도
GitHub Actions로 CI/CD 파이프라인을 구축하면서 DevOps 영역에도 발을 담그게 됐다. 코드 커밋부터 배포까지 자동화하는 과정이 이렇게 복잡하고도 중요한 일인지 몰랐다. 내 코드가 정상적으로 돌아간 것 보다, 팀원들이 "이거 이렇게 설정 다 해주시니까 너무 편해서 좋은데요?" 한마디가 더 기분 좋았다.
협업 스킬
8명이라는 비교적 큰 팀에서 협업하면서 소통의 중요성을 다시 한번 깨달았다. 기술적인 역량도 중요하지만, 역시나 팀원들과 원활하게 소통하고 서로 합의점을 찾아가는 능력이 프로젝트 성공에 더 주요한 영향을 끼친것 같다.
앞으로의 계획
개선 사항
- 이벤트 기반 아키텍처 도입 : 서비스 간 직접 호출을 줄이고 이벤트 기반으로 통신하는 구조로 개선
- 분산 트레이싱 시스템 구축 : Zipkin이나 Jaeger 같은 도구로 요청 추적 시스템
- 성능 최적화 : 데이터베이스 인덱싱, 쿼리 최적화, 캐싱 전략 개선
우선 개선 사항이나, 앞서 말한 ELK 등 아쉬웠던 부분들을 하나씩 리팩토링 해보고 싶다. 단순히 어떤 기술을 사용했다가 아닌, 이전 코드들의 문제점들을 예상해보고, 보다 나은 서비스를 위한 기술을 적용해나가보려 한다.
마치면서..
두 달이라는 시간이 길다면 길고 짧다면 짧았지만, 정말 많은 것을 배우고 성장할 수 있었던 프로젝트였다. 특히 혼자서는 경험할 수 없는 협업과 MSA 아키텍처를 직접 구현해볼 수 있어서 너무 값진 경험이었다.
무엇보다 함께 고생한 팀원들에게 감사하다. 각자 맡은 부분에서 최선을 다해주었고, 정말 고생하면서 똘똘 뭉쳐 끝까지 프로젝트를 완성해낸 우리 팀 8명 정말 자랑스럽다.
Book1lluwa 프로젝트는 끝났지만, 이 경험을 바탕으로 더 큰 도전을 해보고 싶다. 꾸준히 발전하고 성장해서 언젠가는 실제 서비스 운영에 참여하는 것이 목표다.
- 프로젝트 링크 : https://book1lluwa.store/
- API 문서 : https://book1lluwa.store/docs.html
- 소개 영상 : https://youtu.be/Mm8H87yzw7I