아이디어와 인사이트

AI 퍼스트 개발, 코딩 가드레일, 그리고 폐기 가능한 아키텍처를 탐구합니다.

Gherkin 스펙은 당신에게 거짓말을 하고 있다

Gherkin 스펙은 사람이 수동으로 업데이트하도록 맡기는 순간부터 동기화가 깨지기 시작한다. 여기서 feature 파일을 정직하게 유지하는 자동화 검증 방법을 소개한다.

Gherkin 스펙은 당신에게 거짓말을 하고 있다. 의도적으로 그러는 것은 아니다. 처음에는 충실하게 출발했다. 하지만 6개의 스프린트가 지나고 누군가 checkout 흐름을 리팩토링하면서 스텝을 업데이트하는 것을 잊었다. 파일은 여전히 통과한다. 왜냐하면 step…

컴파일러가 문법을 검사한다면, 테스트는 아키텍처를 검사해야 한다.

대부분의 팀은 아키텍처 규칙을 위키에 문서화한다. 의존성 그래프가 어긋났을 때 CI를 실패시키는 실행 가능한 테스트로 작성하는 방법을 알아보자.

테스트 스위트는 이 올바른 입력을 받았을 때 42를 반환하는지 검증한다. 하지만 가 를 import해도 되는지는 검증하지 않는다. 컴파일러는 둘 다 문제없다고 판단한다. 단위 테스트도 둘 다 통과한다. 그러나 둘 중 하나는 아키텍처 위반이며, 6개월 후 당신에게 일주일간의 리팩토링을…

도메인 계층이 Postgres를 임포트합니다. CI는 신경 쓰지 않습니다.

클린 아키텍처 다이어그램은 화이트보드 위에서 멋지게 보입니다. 빌드 파이프라인이 의존성 방향을 강제하여 도메인 코드가 인프라에 닿지 못하도록 만드는 방법을 소개합니다.

팀원 중 누군가가 에 를 임포트했다. PR은 컴파일된다. 테스트는 통과한다. 코드 리뷰는 삼백 줄짜리이고, 아묘도 눈치채지 못한다. 세 달 뒤, 도메인 로직을 공유 패키지로 추출하려 한다. 불가능하다. Postgres 타입, 커넥션 풀링 로직, 모노리스에만 존재하는 커스텀 드라이버…

아키텍처 다이어그램은 이미 거짓말이다

아키텍처 문서는 저장하는 순간부터 썩기 시작한다. 코드로부터 다이어그램을 생성하고, ADR을 남기며, 자동화된 아키텍처 테스트로 문서를 정직하게 유지하는 방법을 알아본다.

위키에서 본 모든 아키텍처 다이어그램은 틀렸다. 극적으로 틀린 건 아니다. 조용히, 점진적으로 틀린 것이다. "Auth"라고 적힌 서비스는 6개월 전에 세 개의 microservice로 쪼개졌다. "sync call"이라고 표시된 화살표는 이제 queue를 통해 async로 동작한다.…

재시도 루프는 첫 번째 요청이 실패했다고 가정합니다. 아마도 그렇지 않았을 겁니다.

타임아웃이나 크래시가 API 요청이 사라졌다는 뜻은 아닙니다. idempotency key가 재시도를 안전하게 만드는 방식, 그리고 실제로 중복을 방지하는 저장소 패턴을 소개합니다.

요청을 처리하던 중 서비스가 크래시됩니다. 클라이언트는 타임아웃을 보고 재시도합니다. 이제 두 건의 결제가 생겼습니다. 고객은 화가 났습니다. 데이터베이스는 일관적입니다. 비즈니스 로직은 그렇지 않습니다. 이건 엣지 케이스가 아닙니다. 분산 시스템의 기본 동작입니다. 네트워크는 패킷을…

프로세스보다 오래 사는 락: 분산 리스가 실제로 어떻게 동작하는지

서버를 재시작하면 인메모리 뮤텍스는 사라진다. 펜싱 토큰과 TTL을 갖춘 분산 리스가 크래시 이후 중복 작업을 어떻게 막는지, 그리고 여전히 물러서는 지점은 어디인지 설명한다.

는 를 견디지 못한다. OOM, 배포 롤아웃, 노드 재부팅에서도 살아남지 못한다. 프로세스가 종료되는 순간 락은 사라진다. 그 락이 예약된 잡, 데이터 마이그레이션, 리더 선출을 보호하고 있었다면, 이제 두 프로세스가 각자가 유일하게 실행 중이라고 믿게 된다. 이건 뮤텍스의 버그가…

goroutine도, timer도, background overhead도 없는 circuit breaker

대부분의 circuit breaker 라이브러리는 복구를 probe하기 위해 background thread를 생성한다. 그럴 필요 없다. 이 글에서는 correctness를 희생하지 않으면서 모든 background overhead를 제거하는 request-driven 설계를 소개한다.

내가 리뷰한 모든 프로덕션 circuit breaker는 결국 background thread를 생성한다. Go의 goroutine일 수도, Java의 일 수도, Rust의 tokio task일 수도 있다. 하는 일은 항상 같다: 몇 초마다 깨어나 downstream service가…

웹 서비스에 그레이스풀 셧다운 경로가 있다. 그게 버그다.

크래시 온리 소프트웨어는 모든 실패를 크래시로, 모든 시작을 복구로 취급한다. 웹 서비스에 적용하면, 셧다운 로직을 삭제하고 kill -9를 견디는 상태를 설계하는 것을 의미한다.

웹 서비스에는 셧다운 핸들러가 있다. 버퍼를 플러시하고, 연결을 닫고, 체크포인트를 기록한다. 한 번쯤 테스트했을지도 모른다. 프로덕션에서는 계획된 배포 중 1년에 한 번 정도 실행될 것이다. 나머지 시간에는 서비스가 OOM 킬, 노드 축출, 정전, 또는 타임아웃으로 SIGKILL을…

Mutant가 무엇을 바꿨는지 모를 때 Surviving Mutant를 죽이는 방법

Mutation testing이 survivor를 찾았는데, 그 mutation이 도대체 무엇을 하는지 전혀 모르겠다. Mutant를 먼저 이해하지 않고도 올바른 테스트를 작성하는 단계별 방법이다.

Mutation testing 리포트는 survivor로 가득 차 있고, 그중 최소 하나는 도저히 이해할 수 없는 상태다. 도구는 47번째 줄의 를 로 뒤집었거나, 전체 조걸문 블록을 로 바꿨거나, 테스트 대상인지도 몰랐던 문자열 리터럴을 mutate했다고 말한다. diff를 세…

인증 코드는 90% mutation coverage가 필요합니다. 문자열 유틸리티는 아닙니다.

전체 코드베이스에 단일 mutation score를 강제하는 것이 왜 실수인지, 그리고 실제 리스크에 맞는 모듈별 임계값을 설정하는 방법.

전체 코드베이스에 단일 mutation score를 강제하는 것은 팀이 테스트를 싫어하게 만드는 최고의 방법입니다. 일반적인 저장소에 PIT이나 Stryker를 실행하면 같은 패턴이 보입니다: 인증 모듈은 40%를 기록하고, 문자열 유틸리티는 95%를 찍으며, ORM 계층은 60%대…