Tooling · Architecture
코드로서의 컴플라이언스:
아키텍처를 강제하는 Harness 만들기
아무도 기계적으로 검사하지 않는 아키텍처 문서는 그냥 절차만 복잡한 제안일 뿐이다. 진짜 흥미로운 엔지니어링 문제는 문서를 쓰는 게 아니라, 코드가 그 문서를 따르지 않는 순간을 자동으로 알려주는 무언가를 만드는 것이다.
설계 문서는 주석과 똑같은 방식으로 부패한다 — 조용히, 조금씩, 그러다 어느 날 누군가 문서를 읽고 코드를 읽고서 몇 달째 서로 다른 두 시스템을 설명하고 있었다는 걸 알아차릴 때까지. 여기서 실제로 효과가 있었던 해법은 더 나은 문서를 쓰는 게 아니었다. Harness를 만드는 것이었다 — 실제 코드가 문서화된 규칙을 따르는지 정적으로 검사하는 자동화된 평가기로, 드리프트를 잡아내는 데 사람의 리뷰가 전혀 필요 없다.
Harness 규칙이 가정해도 되는 것
이 저장소의 examples/ 디렉터리에 있는 비즈니스 예제 — Account 도메인, 그리고 이후의 Card와 Payment — 는 예시용 샘플일 뿐, Harness가 의존해도 되는 fixture가 아니다. Harness 규칙은 "Account 도메인은 이렇게 동작한다"는 것을 전제로 삼아서는 절대 안 되며, 그 형태를 가진 어떤 도메인에도 적용될 수 있는 방식으로 아키텍처 패턴을 검사해야 한다.
Harness는 아키텍처 규칙 준수 여부 — 계층 배치, 의존성 방향, 네이밍, 트랜잭션 경계, Outbox 패턴 — 만 평가하며, 그 구조 안의 비즈니스 로직이 우연히 맞는지는 절대 평가하지 않는다. 주문 취소 규칙, 결제 승인 조건, 재고 예약 정책 — 이런 것들은 전부 범위 밖이다. 이런 내용은 문서의 예시나 실행 가능한 examples/ 코드에는 등장할 수 있지만, 핵심 규칙의 필수 전제가 되어서는 절대 안 된다. 그 선을 흐리면, 새 평가기가 특정 비즈니스 도메인 하나에 조용히 종속되어 버리고, Harness는 프레임워크에 구애받지 않는 아키텍처 가이드에서 Account 서비스 전용 린터로 전락한다.
하나의 정답 구현보다 여러 개의 Assertion
Harness는 "이 파일은 반드시 이렇게 생겨야 한다"는 하나의 레퍼런스 구현을 고정해두는 방식보다, 작고 독립적인 여러 assertion으로 이루어진 부분 점수 방식을 선호한다. 각 규칙은 특정 위반이 존재하는지를 개별적으로 검사하고 그 결과를 합산해 점수를 매긴다 — 같은 원칙을 구현하는 방법이 여러 가지 유효하게 공존할 수 있다는 전제이며, Harness는 examples/의 예제와 겉모습만 다를 뿐 구조적으로는 건전한 선택을 벌점 처리해서는 안 된다.
구조적 검사만으로는 충분하지 않은 이유
1세대 Harness 규칙들은 딱 예상 가능한 것들을 검사했다: domain 폴더가 있는가, Interface 계층이 Infrastructure를 직접 import하지 않는가, Repository 인터페이스가 문서에 명시된 위치에 있는가. 이것만으로도 한 부류의 드리프트 전체를 잡아낼 수 있다 — 그리고 정확히 그만큼의 사각지대도 존재한다. 올바른 파일 안에서 메서드 이름이 올바르게 지어졌는지, 올바른 폴더 안의 클래스가 올바른 것에 의존하는지에 대해서는 아무것도 말해주지 않는다.
실제 감사에서 정확히 이 틈이 드러났다: Repository 메서드 네이밍 컨벤션(목록 조회는 항상 find<Noun>s, 저장은 항상 save<Noun>)이 다섯 언어 구현 중 네 곳에서, 매번 다른 방식으로 위반되어 있었다 — 그중 두 곳은 명사 없이 save만 사용하고 있었고, 네 곳은 더 오래된 도메인에 "전용 findOne과 별도의 findAll" 쌍이 되살아나 있었다. 그 파일들은 하나같이 당시 존재하던 모든 구조적 규칙을 통과했는데, 그 규칙들 중 어느 것도 메서드 이름을 들여다본 적이 없었기 때문이다.
같은 부류의 드리프트를 여러 라운드에 걸쳐 발견하면서 반복적으로 내려진 진단: 이 특정한 것을 검사하는 도구가 없었다. 구조적 검사는 배치를 검증한다. 네이밍 컨벤션, 이미 올바른 폴더 내부의 의존성 방향, 정확한 메서드 이름 패턴에 대해서는 아무것도 말해주지 않는다 — 이것들 각각은 전용 규칙이 필요했고, 그 규칙은 수동 감사가 그 틈을 최소 한 번 직접 찾아낸 뒤에야 작성될 수 있었다.
꼼꼼한 감사조차 놓치는 실패 유형
같은 문제의 더 까다로운 버전도 있다: "코드가 자신의 문서와 일치하는가"를 검사하는 감사는, 코드와 문서가 함께 틀려 있어도 깔끔하게 통과할 수 있다. 한 구현체 자신의 CQRS 문서는 Query Handler가 쓰기 가능한 Repository를 직접 사용하는 것을 올바른 예시로 담고 있었다 — 코드는 문서와 완벽하게 일치했고, 둘 다 Query 쪽은 쓰기 가능한 인터페이스를 절대 봐서는 안 된다는 루트 원칙을 위반하고 있었다. 문서와 코드의 일치 여부만 검사하는 감사는 구조적으로 이를 잡아낼 수 없는데, 일치 그 자체가 바로 그 감사가 검사하는 대상이고, 여기서는 그 일치 자체가 버그였기 때문이다.
실제로 이를 드러낸 건 로컬 문서를 루트 원칙과 비교하는 것이었다 — 그리고 이 비교가 일회성 수동 점검이 아니라 상시 규칙이 되자, 처음 실행했을 때 다른 파일들에서도 같은 부류의 위반을 독립적으로 잡아냈다.
구조적 규칙 역시 놓치는 언어 간 불일치
더 미묘한 사각지대도 있다: 언어를 하나씩 순서대로 감사하는 방식으로는 언어 사이에만 존재하는 구조적 불일치를 절대 볼 수 없다. 알림 발송이라는 관심사가 다섯 언어 구현 중 두 곳에서는 domain 모듈 안에, 나머지 세 곳에서는 별도의 공유 최상위 모듈에 자리 잡고 있었다 — 어느 쪽 선택도 그 자체만 보면 명백히 틀린 건 아니었고, 각자 자기 언어의 Harness는 깔끔하게 통과했으며, 이 불일치는 애초에 단일 언어 리뷰만으로는 구조적으로 드러날 수 없었다. 같은 개념을 다섯 언어 전체에 걸쳐 나란히 비교했을 때에야 비로소 드러났는데, 이런 비교는 "이 코드베이스 하나를 자신의 문서와 비교해 리뷰한다"는 것과는 근본적으로 다른 종류의 감사다.
발견 사항을 영구적인 규칙으로 바꾸기
여러 라운드에 걸쳐 유지된 패턴은 이랬다: 실제 위반 사례를 손으로 한 번 찾아내고, 고치고, 그것을 잡아냈을 Harness 규칙을 작성한 뒤 — 코드베이스가 이제 깨끗하다고 가정하기 전에 그 새 규칙을 즉시 실행하는 것. Repository 네이밍 규칙이 가장 명확한 사례다: 그것이 프로즈가 아니라 기계적 검사로 존재하게 되자마자, 아무도 다시 확인할 생각을 못 했던 도메인 — 다섯 언어 중 세 곳의 인증(authentication) 도메인 — 에 돌려보니 이전의 어떤 감사 범위에도 포함된 적 없던 실제 위반 사례 세 건이 곧바로 드러났다. 모든 이전 감사가 사람들이 계속 신경 쓰던 두 비즈니스 도메인으로만 범위가 한정돼 있었기 때문이다. 의존성 방향 규칙, ID 포맷 규칙, 에러 응답 스키마 규칙, soft-delete 필터 규칙 — 다섯 언어에 걸쳐 이런 식으로 약 서른 개의 규칙이 쌓였고, 각각은 이 사례처럼 가상의 버그가 아니라 실제 버그에서 태어났다.
이 수확은 시간이 지날수록 줄어드는데, 이는 예상된 일이지 이 작업이 더 이상 가치가 없어졌다는 신호가 아니다. 초기 라운드에서는 새 규칙 카테고리마다 실제 위반 사례를 서너 건씩 찾아냈지만, 이 작업의 네 번째 라운드에 이르러서는 새 규칙 대부분이 아무것도 찾아내지 못했다. 손쉽게 잡을 수 있는 언어 간 드리프트가 이미 정리되었기 때문이다. 수확 체감이지, 수확이 0이 되는 건 아니다 — 하루 오후를 들여 작성했지만 오늘은 아무것도 잡아내지 못하는 규칙이라도, 다음 달 찾아올 회귀를 막아내는 파수꾼 역할은 여전히 하고 있는 셈이다.
문서 하나만으로는 절대 얻을 수 없는 것
모든 변경마다 Harness를 실행하는 CI 파이프라인이 있으면, 계층 배치나 네이밍 컨벤션이나 의존성 방향을 위반한 pull request는 사람이 리뷰에서 알아차리기도 전에 빌드가 실패한다 — 린터가 리뷰어가 손으로 지적하기 전에 문법 오류를 잡아내는 것과 같은 방식이다. 이 저장소가 유지하는 셀프 리뷰 체크리스트는 의도적으로 평가기 스펙을 겸하도록 작성돼 있다: 새로운 체크리스트 항목이 추가될 때마다 "이것도 기계적으로 검증할 수 있는가"를 먼저 묻고, 그렇지 않을 때만 프로즈 전용 항목으로 받아들인다.
docs/harness.md — Harness 자체의 설계 원칙 전문 · docs/checklist.md — 이 규칙들 대부분이 만들어진 원천인 셀프 리뷰 체크리스트