Loopers 9

WIL - 9주차 (같은 best-effort라도, 어떤 방향으로 깨지는지가 설계다)

이번 주에 새로 배운 것"best-effort니까 괜찮다"는 설계가 아니다Kafka Consumer에서 DB에 메트릭을 적재하는 기존 파이프라인에 Redis ZSET 랭킹 점수를 추가해야 했다. try-catch로 감싸면 Redis가 죽어도 DB 트랜잭션은 안 깨진다. "best-effort니까 이 정도면 충분하지 않을까?"이 판단이 틀렸다. 같은 best-effort인데 ZINCRBY를 TX 안에 넣느냐, TX 커밋 후에 넣느냐에 따라 결함의 방향이 달랐다.TX 안에서 ZINCRBY → TX COMMIT 실패 시 → Redis에는 반영됨, DB에는 안 됨 → 재처리 시 → double increment (over-count)TX 커밋 후 ZINCRBY → COMMIT 성공 ..

스터디/루퍼스 2026.04.11

WIL - 7주차 (이론값과 실측값 사이에서 자기 생각을 만드는 법)

이번 주에 새로 배운 것"교과서대로 했는데 안 됐다"가 가장 많이 가르쳐줬다이번 주 루퍼스 과제는 EDA + Kafka Outbox Pipeline이었다. 핵심 트랜잭션과 부가 로직을 분리하고, Outbox Pattern으로 시스템 간 이벤트를 신뢰성 있게 전달하는 것.교과서적 답은 알고 있었다. @TransactionalEventListener(AFTER_COMMIT)으로 TX 커밋 후 이벤트를 발행하면 된다. 그래서 AFTER_COMMIT에서 Outbox에 저장했다. 실행되지 않았다. TransactionTemplate.execute() 반환 시점에 TX가 이미 끝나서, 리스너가 바인딩할 TX 컨텍스트가 없었다.그래서 BEFORE_COMMIT으로 바꿨다. 동작했다. 하지만 "주문 확정 이벤트"가 확정..

스터디/루퍼스 2026.03.29

WIL - 6주차 (모르는 것을 모른다고 말할 수 있게 되기까지)

이번 주에 새로 배운 것"모른다"를 설계에 담는 법이번 주 루퍼스 과제는 PG 연동이었다. 결제 요청을 보내고 응답이 안 오면 어떻게 할 것인가. 처음엔 단순하게 생각했다. 타임아웃이 나면 실패로 처리하고 롤백하면 되지 않나.틀렸다. 타임아웃은 실패가 아니다. "모른다"는 뜻이다. PG가 요청을 아예 못 받았을 수도 있고, 받아서 승인까지 했는데 응답만 유실됐을 수도 있다. 이 세 가지 가능성을 구분할 수 없는 상태에서 "실패"로 단정하면, 사용자 카드에서는 돈이 빠졌는데 주문은 취소되는 사고가 난다. 반대로 "성공"으로 단정하면, 돈을 안 받았는데 상품이 나간다.결국 UNKNOWN이라는 상태를 만들었다. "아직 모른다"를 명시적으로 표현하는 상태다. 모르면 행동하지 않고, 알아낸 다음에 행동한다. 이 ..

스터디/루퍼스 2026.03.22

WIL - 5주차 ("왜?"를 멈추지 않았더니 보이기 시작한 것들)

이번 주에 새로 배운 것"왜?"라는 질문이 깊이를 만든다이번 주는 유독 "왜?"를 많이 물었다. 복합 인덱스를 공부하다가 "카디널리티가 높은 컬럼을 앞에 놓으라"는 규칙을 만났다. 예전 같았으면 그대로 외웠을 것이다. 그런데 이번엔 "왜?"를 던져봤다. 왜 카디널리티가 높으면 앞이어야 하지? B+Tree에서 실제로 어떤 차이가 생기지? 등호 조건과 범위 조건이 섞이면 어떻게 되지? 파고 들어가니까 답이 달라졌다. 카디널리티보다 등호 조건이 먼저라는 게 진짜 규칙이었다. 카디널리티가 아무리 높아도 범위 조건이면 그 뒤 컬럼은 인덱스를 못 탄다. B+Tree의 리프 노드가 정렬되는 방식을 이해하고 나니, "왜 그런지"가 보였다. 외운 규칙이 아니라 원리에서 나온 판단이 되니까, 새로운 상황을 만나도 스스로 ..

스터디/루퍼스 2026.03.13

WIL - 4주차 (환경을 바꿀 수 없을 때, 나를 바꾸는 법)

이번 주에 새로 배운 것이상과 현실 사이에서 할 수 있는 건 생각보다 많다회사에서 새 프로젝트가 들어왔다. 보험 도메인이다. 처음 기획서를 받았을 때 정책이 빠져 있는 부분이 많았고, 고려사항도 불명확했다. 예전 같았으면 "기획이 덜 된 거 아닌가"라고 생각하고 기다렸을 것이다.이번엔 다르게 움직였다. AI를 활용해서 보험 도메인의 일반적인 정책, 예외 케이스, 고려해야 할 사항들을 먼저 정리했다. 다른 프로젝트에서 사람들이 어떤 부분을 놓쳤는지, 어떤 정책이 나중에 문제가 됐는지를 조사하고 참고했다. 그걸 바탕으로 기획 쪽에 질문을 던지고, 정책을 같이 구체화해나갔다.결과는 예상 밖이었다. 정책이 명확해지니 단순 CRUD는 물론이고, 복잡한 비즈니스 로직도 흐름이 보였다. 데드라인보다 빠르게 치고 나갈..

스터디/루퍼스 2026.03.08

WIL - 2주차 유비쿼터스 언어를 먼저 고정하니 설계가 따라왔다

이번 주에 새로 배운 것이번 주 과제는 이커머스 도메인(상품, 브랜드, 좋아요, 주문 등)을 대상으로 설계 산출물만으로 PR을 제출하는 것이었다. 요구사항 정리부터 시퀀스 다이어그램, 클래스 다이어그램, ERD까지. 코드 없이 설계를 완성한다는 게 처음엔 막막했다.그래서 나는 시작점부터 명확히 잡았다.유비쿼터스 언어(Glossary)를 가장 먼저 정의하고, 그 언어 위에서 설계를 진행한다.이 선택은 “문서 작성 순서”가 아니라 “설계의 품질을 결정하는 규약”을 먼저 고정하는 일이었다.실무에서 설계가 흔들리는 순간을 떠올려보면, 대부분은 기술이 아니라 단어의 혼용에서 시작한다.“상품”과 “아이템”을 같은 의미로 쓰거나“주문”과 “결제”를 동일시하거나“장바구니”와 “주문서” 경계를 흐리거나이런 상태로 ERD..

스터디/루퍼스 2026.02.15

WIL - 1주차 (TDD & Hello Agent)

WIL - 1주차 (TDD & Hello Agent)이번 주에 새로 배운 것TDD는 “테스트 먼저”가 아니라, 설계를 이끌어내는 도구였다과제를 시작할 때 “기능 구현에 앞서 테스트 코드를 먼저 작성하라”는 요구를 받았다. 처음에는 단순히 “테스트를 먼저 쓰고 통과시키면 되겠지”라고 생각했다. 그런데 막상 시작하니, 어디서부터 어떤 순서로 무엇을 테스트해야 하는지부터 불명확했다.처음에는 요구사항을 기준으로 기능 단위로 구현하려 했다. 하지만 곧 흐름이 무너졌다. PasswordValidator부터 시작해 검증 로직 → Entity → Service → Controller 순서로 접근하려니, 기능이 이곳저곳에 흩어졌다. 결과적으로 “지금 내가 무엇을 검증하고 있는가?”를 잃어버린 상태가 됐다.방향을 다시 잡..

스터디/루퍼스 2026.02.08

AI와 TDD를 함께하며 배운 것 - 개발자의 역할은 어떻게 바뀌는가

TL;DRAI와 협업하면서 개발자의 역할이 "코드를 작성하는 사람"에서 "의도를 정의하고 판단하는 사람"으로 바뀌고 있음을 체감했다. 단, 테스트 코드만큼은 직접 작성해야 진짜 내 것이 된다.왜 이 글을 쓰게 되었는가?1주차 과제를 Claude Code와 함께 진행했다. 처음에는 "AI가 코드를 다 짜주겠지"라는 막연한 기대가 있었다. 하지만 실제로 협업해보니, AI를 잘 쓰려면 내가 더 명확해야 했다.무엇을 만들지, 어떤 구조로 할지, 어디까지 허용할지 — 이런 판단들을 내가 하지 않으면 AI는 엉뚱한 방향으로 달려갔다. 이 글에서는 AI와 TDD를 함께하며 느낀 점들을 정리해본다.CLAUDE.md - AI에게 규칙을 알려주는 것부터 시작Claude Code를 사용할 때 가장 먼저 한 일은 CLAUDE..

스터디/루퍼스 2026.02.06

단위 테스트를 넘어서 - 통합 테스트가 잡아준 JPA 버그 이야기

테스트는 통과했는데 버그였다 - 트랜잭션 경계에서 배운 것 TL;DRMock 테스트의 한계를 마주한 순간 - JPA Detached Entity 디버깅 경험단위 테스트(Mock)에서는 절대 발견할 수 없는 버그가 있다. 비밀번호 변경 기능이 "성공"했는데 DB에 반영되지 않는 버그를 통합 테스트를 작성하면서 발견했고, 원인은 JPA의 Detached Entity 상태였다.왜 이 글을 쓰게 되었는가?1주차 과제로 회원가입, 내 정보 조회, 비밀번호 변경 API를 TDD로 구현하고 있었다.단위 테스트는 모두 통과했다. E2E 테스트도 작성해뒀다. 그런데 Facade 통합 테스트를 작성하는 과정에서 이상한 현상을 발견했다.비밀번호 변경 API 호출 → 성공 응답새 비밀번호로 로그인 → 실패이전 비밀번호로 로그..