1주차 — 테스트 코드 & TDD
TDD를 배우기 위해 돈까지 냈던 적이 있다. 그러면서도 막상 TDD를 즐겨 쓴 적은 없었다. 회사에서 테스트 문화를 만들어보려고 시도도 해봤지만 흐지부지됐다. 인식의 벽은 생각보다 두꺼웠고, 내가 그 벽을 깰 능력조차 당시에는 없었다.
그런데 AI가 등장하면서 분위기가 조금 바뀐 것 같다. 반복적이고 귀찮은 테스트 코드 작성을 AI가 상당 부분 대신해주니, 진입 장벽이 낮아졌다. 덕분에 이번 주차는 예전보다 훨씬 편하게 테스트를 먼저 써볼 수 있었다. 그리고 막상 과제를 하면서 테스트를 먼저 쓰는 게 단순히 순서 문제가 아니라는 걸 다시 한번 느꼈다. 구현보다 먼저 어떻게 동작해야 하는가를 정의하다 보면, 설계가 얼마나 테스트하기 어렵게 짜여졌는지가 바로 드러난다. 의존성을 내부에서 직접 생성하거나, 한 함수에 책임이 너무 많으면 테스트 자체가 불가능했다. 좋은 테스트를 쓰려면 결국 좋은 구조를 만들 수밖에 없다는 걸, 머리가 아닌 손으로 느낀 주차였다.
2주차 — 소프트웨어 디자인
개발을 시작하기 전에 문서를 먼저 작성하는 것. 말로는 알고 있었지만 실천은 잘 못했다. 현재 회사도 문서 문화가 아쉽게도 조금 부족한 편이고, 나 역시 나름대로 작성하려고 했지만 돌아보면 부실하게 넘어간 부분들이 꽤 있었다.
2주차를 공부하면서 내가 무엇을 빠뜨리고 있었는지가 보이기 시작했다. 시퀀스 다이어그램, ERD, 유비쿼터스 언어. 이것들이 단순히 문서를 잘 쓰기 위한 도구가 아니라, 팀 전체가 같은 언어로 같은 개념을 이야기하기 위한 기반이라는 걸 알게 됐다. 나중에 "제가 요청한 것과 다른데요?"라는 말을 듣지 않으려면, 코드 이전에 이 언어가 맞춰져 있어야 한다.
그 이후로 개발 업무를 시작하기 전에 문서를 먼저 작성하는 습관이 생겼다. 테스트 코드처럼 문서 작성도 AI의 도움으로 생각보다 수월해졌는데, 이것도 루퍼스 부트캠프에서 얻은 팁 중 하나다.
3주차 — 도메인 모델링 & 아키텍쳐
도메인 모델링도 중요했지만, 이번 주차에서 더 인상 깊었던 건 레이어드 아키텍처였다. Interfaces → Application → Domain → Infrastructure. 계층을 나누는 건 알고 있었는데, 각 계층이 왜 그 방향으로만 의존해야 하는지는 제대로 생각해본 적이 없었다.
핵심은 DIP, 의존성 역전 원칙이었다. Domain이 Infrastructure를 직접 의존하면, DB나 외부 기술이 바뀔 때 비즈니스 로직까지 흔들린다. 반대로 Domain에 인터페이스를 두고 Infrastructure가 그것을 구현하도록 뒤집으면, 도메인은 외부 기술로부터 완전히 독립된다. 테스트할 때 FakeRepository 로 갈아끼울 수 있는 것도 이 구조 덕분이다.
사실 이 구조는 Spring을 쓰면서 어느 정도 자연스럽게 따라가고 있었다. 그런데 "왜 이렇게 해야 하는가"를 명확히 설명할 수 없었다. 이번 주차 덕분에 내가 그동안 관성적으로 써온 패턴에 이유가 생겼다.
도메인을 설계하면서 한 가지 선택지가 생겼다. 도메인 모델을 @Entity로 쓸 것인가, 아니면 JPA에 의존하지 않는 순수 자바 객체로 쓸 것인가. 나는 후자를 선택했다. @Entity를 쓰면 편리하지만, 도메인 객체가 JPA 어노테이션에 종속되고 테스트도 그만큼 무거워진다. 순수 자바 객체로 도메인을 만들면 Entity, Value Object, Domain Service 각각의 책임이 더 명확해지고, Spring 없이도 도메인 로직을 테스트할 수 있다. 다만 코드 분량이 확실히 늘어난다는 트레이드오프가 있었다. 그래도 양쪽의 장단점을 직접 부딪히며 알게 된 게 수확이었다. 상황에 따라 선택할 수 있는 근거가 생겼으니까.
4주차 — Transactional Operation
솔직히 예전엔 동시성 이슈가 언급되면 "비관적 락이나 낙관적 락 쓰면 되는 거 아니야?"라고 단순하게 생각했다. 해결책의 이름은 알았지만, 왜 그 문제가 발생하는지는 제대로 생각해본 적이 없었다. 이번 주차에서 그 근본 원인을 처음 제대로 짚었다. Read-Modify-Write 패턴. 값을 읽고, 수정하고, 다시 쓰는 이 세 단계가 원자적으로 처리되지 않을 때 동시성 문제가 생긴다. 원인을 알고 나니 락 전략이 왜 필요한지도 비로소 납득이 됐다.
낙관적 락과 비관적 락을 비교하면서 느낀 건, 둘 중 하나가 정답이 아니라 "이 자원이 얼마나 자주 충돌하는가", "실패를 허용할 수 있는가"에 따라 선택이 달라진다는 것이었다. 그리고 동시성을 해결하는 방법이 비관적 락, 낙관적 락만이 아니라는 것도 알게 됐다. 원자적 쿼리를 사용하면 락 없이도 많은 동시성 문제를 해결할 수 있고, 구현도 비교적 간단하다. 오히려 락보다 먼저 고려해볼 만한 선택지라는 생각이 들었다.
아쉬운 점이 있다면, 동시성 해결에 집중하다 보니 @Transactional 을 깊게 공부하지 못했다는 것이다. 매우 중요한 개념이라는 걸 알기에, 수료 후 잘 작성된 3기 라이팅 과제들을 읽으며 따로 채워볼 생각이다.
5주차 — Practical Read Optimization
서비스 성능을 가장 크게 좌지우지하는 건 결국 조회다. 쓰기는 아무리 많아도 읽기의 몇 분의 일에 불과하고, 사용자가 체감하는 속도도 대부분 조회에서 갈린다. 그만큼 중요한 파트인데, 개인적으로는 쉬운 것 같으면서도 막히는 부분이 많아 묘하게 어려운 주차였다.
사실 현재 회사 서비스는 트래픽이 많지 않아서 조회 성능이 문제가 되는 경우가 별로 없었다. 그러다 보니 문제가 생기면 "인덱스 걸면 되겠지"라고 너무 가볍게 생각하고 넘어간 적이 많았다. 이번 주차를 통해 그 안일함이 깨졌다. 인덱스는 어디에나 걸면 되는 게 아니라, 어느 컬럼에, 어떤 순서로, 어떤 조회 패턴에 맞게 설계해야 하는지가 핵심이었다. 잘못 걸면 오히려 쓰기 성능을 갉아먹고, 심지어 인덱스가 있어도 안 쓰이는 경우도 있다는 걸 알게 됐다.
6주차 — Failure-Ready Systems
Timeout, Retry, Circuit Breaker. 이름은 들어봤지만 솔직히 굉장히 낯선 개념들이었다. 회사 서비스에도 외부 시스템을 호출하는 곳이 꽤 있는데, 그 중 어디에도 이런 장치가 제대로 갖춰져 있었는지 자신 없다. 그게 다행이라고 느껴지는 것 자체가, 이번 주차를 배우기 전의 내 상태였다. 공부하면서 느낀 건, 이게 엄청난 장애를 막는 거창한 개념이라기보다는 만약의 사태에 미리 대비하는 방법이라는 것이었다. 외부 시스템이 느려지거나 죽었을 때 사용자가 당황하지 않도록 우회할 수 있는 구조를 사전에 만들어두는 것. 장애가 터진 뒤에 수습하는 게 아니라, 터지기 전에 어떻게 동작할지를 설계해두는 태도에 가깝다고 느꼈다.
한 가지 어려웠던 건 Timeout이나 실패율 같은 수치를 어떻게 설정해야 하는가였다. 팀원분들과 같은 기수분들이 성능 테스트하는 방법을 공유해 주셔서 방향을 잡을 수 있었는데, 덕분에 "일단 보수적으로 잡고, 실측 데이터를 보면서 조정한다"는 감각을
얻었다.
앞으로 외부 시스템을 연동할 일이 생기면, 기능 구현보다 이 장치들을 먼저 떠올릴 것 같다. 코드를 짜기 전에 "이 호출이 실패하면 어떻게 할 것인가"를 먼저 생각하는 것. 그게 이번 주차에 배운 점이다
7주차 — Hello, Event! Welcome, Kafka!
이번 주차는 Kafka를 처음 써봤다. Kafka 자체도 낯설었지만, 브로커, 파티션, 컨슈머 그룹 같은 내부 개념들이 한꺼번에 쏟아지니 하나하나 파고들기보다는 전체적인 맥락을 잡는 데 집중했다. 어떤 상황에서 Kafka를 써야 하는지, 왜 단순한 메시지 큐가 아닌지를 이해하는 것만으로도 충분히 의미 있었다.
사실 Kafka는 규모가 크지 않은 회사에서는 쓸 일이 많지 않다. 그럼에도 루퍼스를 통해 EDA 구조를 간접적으로 경험할 수 있어서 재밌었다. 대규모 트래픽에서 트랜잭션을 어떻게 나누고, 서비스 간 결합도를 어떻게 낮추는지를 직접 구현해보는 경험은 쉽게 얻기 어려운 것이었다. 특히 이벤트를 분리하면 새로운 후속 처리가 필요할 때 기존 코드를 건드리지 않고 리스너만 추가하면 된다는 것, 그게 곧 확장성이라는 걸 이번 주차에서 처음 체감했다.
개인적으로 더 와닿았던 건 ApplicationEvent였다. 마침 회사 프로젝트에서도 회원 포인트 처리 쪽에 ApplicationEvent를 쓰는 부분이 있는데, 이번 주차를 배우고 나서 어떤 부분을 개선하면 좋을지가 보이기 시작했다. 곧 반영할 계획이다. Outbox Pattern도 처음 들은 개념이었는데, 이벤트 유실 없이 발행을 보장하는 방법이라는 점에서 중요하다는 게 느껴졌다. 당장 쓸 일이 없더라도 기억해두고 싶은 개념이었다.
8주차 — Please Wait. You’re In The Queue.
개인적으로 가장 재밌게 공부했던 챕터다. 대기열은 티켓팅이나 한정 상품 구매처럼 일상에서 자주 마주치는 개념인데, 그걸 직접 구현해본다는 게 색달랐다. 사용자 입장에서는 그냥 "기다리는 화면"이지만, 그 안에 이렇게 많은 설계가 담겨 있다는 걸 간접적으로 경험한 것만으로도 기억에 남는다.
트래픽이 몰릴 때 "나중에 다시 시도하세요"를 반환하면, 사용자는 오히려 더 자주 새로고침을 누른다. 대기열은 그 악순환을 끊는 구조였다. 순번을 보여주고 예상 대기 시간을 알려주는 것만으로 사람들이 기다린다는 것, 그게 단순한 기술이 아니라 사용자 경험에 대한 배려라는 걸 느꼈다. 처리 속도를 높이는 것보다, 흐름을 제어하는 것이 더 중요할 때가 있다는 것도 이번 주차에서 얻은 시각이었다.
이번 주차에서 Redis의 매력도 새롭게 와닿았다. 지금까지는 "Redis는 캐시"라는 개념으로만 알고 있었는데, Sorted Set으로 순번을 관리하고 TTL로 토큰을 자동 만료시키는 구조를 직접 만들어보니 Redis가 단순한 캐시 저장소가 아니라 상황에 따라 다양하게 활용할 수 있는 도구라는 게 실감났다.
9주차 — Show Me The Ranking
8주차에 이어 Redis의 막강함을 다시 한번 느낀 주차였다. Sorted Set 하나로 실시간 랭킹을 관리할 수 있다는 게 여전히 인상적이었고, Redis가 단순한 캐시를 넘어 얼마나 다양한 문제를 풀 수 있는 도구인지 점점 체감이 됐다.
이번 주차에서 멘토님이 하신 말씀 중에 가장 기억에 남는 말은 "랭킹 시스템은 인기 많은 상품을 정렬하는 게 아니라, 우리 서비스에서 '인기'가 무엇인지를 먼저 정의하는 것이 시작이다"라는 것이었다. 단순히 좋아요 수가 많은 게 인기인지, 판매량인지, 최근 조회수인지. 그 정의에 따라 가중치와 수식이 달라진다. 생각해보면 당연한 말인데, 막상 직접 수식을 산정하려니 쉽지 않았다. 어떤 지표에 얼마의 가중치를 줘야 하는지에 정답이 없다는 게 오히려 어렵게 느껴졌다.
콜드 스타트 문제도 흥미로웠다. 랭킹 윈도우가 바뀌는 순간 모든 점수가 0으로 리셋되면, 아무 상품도 상위에 오를 수 없다. 전날 점수의 일부를 이월해서 자연스럽게 채워주는 방식이 단순하면서도 영리하다고 느꼈다. 랭킹 시스템만이 가진 고유한 문제를 해결하는 방법이라 재밌게 봤다.
10주차 — Collect, Stack, Zip
Spring Batch도 Kafka처럼 처음 써보는 기술이었다. 솔직히 "우리 회사에서 이걸 쓸 일이 있을까?" 라는 생각이 먼저 들었다. 세부적으로 파고들면 어려운 개념들이 많아서, 이번에도 Job, Step, Chunk, Tasklet 같은 전체적인 컨셉을 이해하는 데 집중했다. 언젠가 쓸 일이 생겼을 때 "아, 이런 구조였지"라고 떠올릴 수 있을 만큼만.
모든 걸 실시간으로 처리하고 싶었지만, 수백만 건 데이터를 매 요청마다 집계하는 건 DB를 혹사시키는 일이다. 실시간과 배치를 잘 나누는 것도 설계 능력이라는 걸 알게 됐다. 지금 꼭 실시간이어야 하는지, 하루 한 번 새벽에 계산해도 충분한지. 그 판단 하나가 시스템의 복잡도와 비용을 크게 바꾼다.
앞 챕터에서도 언급됐던 Materialized View가 이번에도 다시 등장했다. 어려운 개념은 아니지만, 복잡한 집계 쿼리를 미리 계산해 저장해두는 이 방식이 적재적소에 잘 쓰이면 서비스 성능에 꽤 큰 도움이 될 것 같다는 생각이 들었다. 단순한 아이디어지만, 언제 어디에 쓸지를 판단하는 게 핵심이라는 점에서 다른 개념들과 닮아있다.
보너스 — 클로드 코드, AI를 쓰는 법을 배우다
루퍼스 부트캠프를 수강하면서 Claude Code를 처음 써봤다. 솔직히 처음엔 그냥 "AI 코딩 도구 하나 더 쓰는 거겠지"라고 가볍게 생각했다. 그런데 멘토분들과 수강생분들이 각자의 팁을 아낌없이 공유해주면서, AI를 잘 쓴다는 게 단순히 도구를 아는 것이 아니라 어떻게 활용하느냐의 문제라는 걸 알게 됐다.
덕분에 AI 사용법에 대한 시야가 넓어졌다. 틈틈이 "어떻게 하면 AI를 더 잘 쓸 수 있을까"를 찾아보게 됐고, 회사에서도 개인적으로 업무 효율을 높이기 위한 AI 워크플로우를 만들어보고 있다. 아직 완성된 건 아니지만, 그런 시도를 자연스럽게 하게 된 것 자체가 루퍼스가 만들어준 변화라고 생각한다.
10주 동안 기술만 배운 게 아니었다. 어떻게 생각하고, 어떻게 설계하고, 어떻게 배워나가야 하는지.
그 태도를 함께 얻었다. 루퍼스에서 보낸 시간이 앞으로도 오래 남을 것 같다.
루퍼스, 진심으로 감사합니다.