
이 활동은 젝트에서 진행한 프로젝트입니다.
젝트 공식 홈페이지 👉 https://ject.kr
젝트 4기 2팀, 첫 절반을 돌아보며 — 1차 활동 회고
들어가며
올해 초, "혼자 공부만 하다 보니 협업다운 협업을 해본 적이 없네"라는 생각이 머릿속을 떠나지 않았다.
그러던 차에 젝트(JECT)를 알게 됐고, 4기에 합류했다. 그렇게 4기 2팀의 백엔드로 한 학기 가까운 시간을 보냈다.
내가 맡은 영역은 한마디로 "사용자가 우리 서비스에 들어오고, 머물고, 떠나는 모든 과정" 이었다.
로그인부터 회원가입, 마이페이지, 그리고 회원 탈퇴까지 — 인증(Auth)과 유저(User) 도메인이 내 담당이었다.
3월 말 첫 커밋부터 6월까지, 돌아보니 어느새 98개의 커밋이 쌓여 있었다.
거창한 회고는 아니지만, 그 사이에 내가 무엇을 만들었고 무엇을 배웠는지 담담하게 적어보려 한다.
온보딩, 그리고 낯선 단어들
처음 레포를 열었을 때 가장 낯설었던 건 헥사고날 아키텍처였다.domain, port, adapter로 폴더가 나뉘어 있었는데, 처음엔 "왜 이렇게까지 나누지?" 싶었다.
컨트롤러에서 바로 서비스를 호출하던 익숙한 방식과 달리, 도메인은 바깥 세상(웹, DB)을 몰라야 한다는 규칙이 있었다.
솔직히 초반엔 그 경계를 자주 넘나들었다. 도메인 객체가 웹 요청 DTO를 알고 있다거나,
서비스가 컨트롤러 사정을 너무 잘 알고 있다거나. 이 "경계 감각"을 익히는 게 온보딩 기간의 첫 숙제였다.
내가 만든 것들
로그인의 시작 — OAuth2와 JWT
가장 먼저 손댄 건 Google 소셜 로그인이었다.
사용자가 구글 계정으로 로그인하면, 우리 서버가 그 사람을 식별할 수 있는 토큰(JWT)을 발급해주는 구조다.
- Access Token: 짧게 살아 있으면서 "이 사람 누구야?"를 매 요청마다 증명
- Refresh Token: Access Token이 만료됐을 때 다시 발급받기 위한 열쇠
말로는 간단하지만, 토큰을 언제 만들고, 어떻게 검증하고, 만료되면 어떻게 재발급할지 흐름을 직접 짜보니
인증이라는 게 생각보다 섬세한 영역이라는 걸 알게 됐다. 이때 처음으로 단위 테스트(JwtProviderTest, JwtAuthFilterTest)도
제대로 써봤는데, "내가 짠 토큰 검증 로직이 진짜 맞나?"를 코드로 확인하는 경험이 꽤 든든했다.
"토큰을 대체 어디에 담지?" — 쿼리 파라미터에서 쿠키로
처음엔 토큰을 URL 쿼리 파라미터로 주고받았다. 동작은 했다. 그런데 어딘가 찜찜했다.
URL에 토큰이 그대로 노출되면 브라우저 히스토리, 서버 로그, 공유 링크 어디에든 남을 수 있었다.
그래서 토큰 전달 방식을 HttpOnly 쿠키로 통째로 바꿨다.
ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", token)
.httpOnly(true) // 자바스크립트로 토큰을 훔칠 수 없게
.secure(true) // HTTPS에서만 전송
.sameSite("...") // CSRF 완화
.domain(".vs.io.kr") // 서브도메인 간 공유
.build();
httpOnly 하나로 프론트엔드 스크립트조차 토큰을 읽지 못하게 막을 수 있다는 걸 알았을 때,
"보안은 거창한 게 아니라 이런 작은 설정의 합이구나" 싶었다. 도메인(.vs.io.kr)·SameSite·Secure 옵션을
하나씩 맞춰가며 쿠키 한 줄에 이렇게 많은 고민이 들어간다는 걸 처음 체감했다.
닉네임에 진심이던 날들 — 추천 닉네임과 욕설 필터
회원가입 단계에선 추천 닉네임을 만들었다. "형용사 + 동물 이름(2자)" 조합으로 자동 생성해서,
사용자가 고민 없이 시작할 수 있게 했다. (데이터셋을 다듬는 커밋이 유독 많았던 건… 동물 이름 고르는 게 의외로 어려웠기 때문이다.)
그런데 닉네임을 자유롭게 바꿀 수 있게 하니, 자연스럽게 따라온 고민이 욕설 필터링이었다.
단순히 contains()로 검사할 수도 있었지만, 단어 수가 늘어날수록 비효율적이라 직접 KMP(Knuth–Morris–Pratt) 알고리즘으로
문자열 매칭기를 구현해봤다. 학교에서 배우기만 했던 알고리즘을 실제 기능에 써본 첫 경험이라 소소하게 뿌듯했다.
이후엔 대소문자를 구분하지 않도록 고치고, 영어 slang 단어까지 사전을 넓혀가며 필터를 다듬었다.
마이페이지, 그리고 떠나는 사람을 배웅하는 법
마이페이지에선 프로필 조회, 이름·이미지 편집, 그리고 내가 참여한 투표(진행 중 / 종료, 최신순·인기순)를 볼 수 있게 했다.
가장 기억에 남는 건 회원 탈퇴 기능이다. 처음엔 "그냥 데이터 지우면 되는 거 아냐?"라고 생각했다.
하지만 사용자가 남긴 투표·기록은 다른 사람들과 얽혀 있어서, 진짜로 지워버리면 곳곳에 구멍이 났다.
그래서 soft delete + 익명화 방식을 택했다. 계정은 떠나되, 남은 흔적엔 본인 정보 대신
"알 수 없음"이라는 닉네임만 남도록 했다. 탈퇴 시각(withdrawnAt)을 따로 기록해 추적도 가능하게 했다.
"떠나는 사람도 잘 배웅하는 게 서비스의 책임"이라는 걸 코드로 배운 순간이었다.
협업하며 느낀 점
혼자 공부할 땐 절대 배울 수 없던 게 PR과 코드 리뷰 문화였다.
내 코드가 develop에 합쳐지려면 PR을 올리고 팀원의 리뷰를 거쳐야 했다.
처음엔 "내 코드에 빨간 줄 그어지는" 느낌이 어색했지만, 곧 그게 가장 빠르게 성장하는 길이라는 걸 알았다.
특히 기억에 남는 건 유저 정보 수정 로직을 리팩토링하면서 받은 피드백이다.
도메인과 웹 어댑터 사이에 DTO가 마구 섞여 있던 걸, 리뷰를 통해 의존 경계를 분리하게 됐다.
온보딩 때 "왜 이렇게 나누지?" 싶었던 그 헥사고날 규칙의 이유를, 직접 리팩토링하면서 비로소 납득했다.
지식으로 아는 것과 손으로 겪어보는 건 정말 달랐다.
부족했던 점과 개선
솔직히 이 기간을 돌아보면 가장 아쉬운 건 '집중'이었다.
프로젝트 중간에 소프트웨어 마에스트로(소마) 17기에 합격하면서, 두 가지 큰 일을 동시에 끌고 가야 했다.
둘 다 욕심나는 일이었지만, 그만큼 어느 한쪽에 온전히 몰입하기가 쉽지 않았다.
시간에 쫓기다 보니 기능 구현이 늘 우선순위에 밀렸고, 결국 테스트 코드를 제대로 작성하지 못한 채
Postman으로 동작만 확인하고 push를 날린 적이 많았다.
"일단 돌아가니까"로 넘어간 코드들은, 나중에 리팩토링하거나 디버깅할 때 부메랑이 되어 돌아오곤 했다.
또 하나 아쉬운 건 AI를 거의 활용하지 않았다는 점이다.
반복적인 부분을 AI에 맡기고 잘 분담했다면 더 간단하고 빠르게 짤 수 있었을 텐데,
"내가 직접 작성하지 않은 코드는 내 것이 아니다"라는 생각에 갇혀 도구를 멀리했다.
지금 돌아보면 모든 줄을 손으로 쳐야 내 실력이 된다는 건 일종의 강박이었던 것 같다.
정말 중요한 건 결과물을 이해하고 책임질 수 있느냐이지, 한 글자도 빠짐없이 타이핑했느냐가 아니었다.
(인프라 쪽에서도 시행착오가 많았다. 구글 OAuth 키나 쿠키 도메인 같은 민감한 값을 GitHub Actions에 주입하는 과정에서
빌드가 자주 깨졌고, 그 덕에 "코드를 짜는 것과 안전하게 돌아가게 하는 건 별개의 능력"이라는 걸 배웠다.)
다음 기간(젝러닝 ~ 데모데이)엔 두 가지를 분명히 바꾸고 싶다.
하나는 기능과 테스트를 함께 가져가는 흐름을 만드는 것, 다른 하나는 AI를 똑똑하게 활용해 본질에 더 집중하는 것이다.
마치며
숫자로 돌아보면 3월 말부터 6월까지 98개의 커밋, 약 +8,000줄.
하지만 그보다 값진 건, 로그인 화면 너머에서 일어나는 일들 — 토큰이 오가고, 쿠키가 보안을 지키고,
누군가 떠나도 그 자리를 정중히 비워두는 — 그 보이지 않는 일들을 내 손으로 만들어봤다는 경험이다.
아직 절반이다. 데모데이까지 남은 기간엔, 지금까지 쌓은 것들을 더 단단하게 다듬고
테스트와 안정성에도 더 욕심을 내보려 한다.
이 활동은 젝트에서 진행한 프로젝트입니다.
젝트 공식 홈페이지 👉 https://ject.kr
'Review' 카테고리의 다른 글
| [ByteByteGo] Database Indexing Strategies(데이터 베이스 인덱스 전략) (0) | 2026.07.12 |
|---|---|
| Ject 2차 회고록 (0) | 2026.07.09 |
| [ByteByteGo] Multi-Region Architecture: Going Global Without Going Broke (0) | 2026.07.05 |
| [ByteByteGo] How AI Agents Manage Memory and Avoid Forgetfulness (0) | 2026.07.04 |