building-backends
이것은 백엔드 작업을 위한 기본 스킬입니다. 서버 코드를 작성하기 전에 반드시 읽어야 합니다. 모든 서비스가 일관성을 유지하도록 기본값과 절대 변경할 수 없는 규칙을 설정하며, 세부 절차는 특정 스킬에 위임합니다. 여기의 규칙이 로컬 레포지토리 관례와 충돌하면 레포지토리가 우선합니다; 충돌이 없으면 이 규칙을 따릅니다.
먼저, 구축하기 전에 이해하세요
- 코드를 추가하기 전에 기존 코드를 읽으세요. 레포지토리의 프레임워크, 오류 모델, 계층 구조, 명명 규칙, 테스트 스타일을 일치시키세요. 코드베이스와 싸우는 "올바른" 패턴은 잘못된 패턴입니다. 레포지토리가 이미 하는 일을 두 번째 방식으로 도입하는 것은 개선이 아니라 결함입니다.
- 구현이 아닌 계약을 명확히 하세요. 설계를 선택하기 전에 입력, 출력, 오류 사례, 호출자, 일관성/지연 시간 기대치를 알아야 합니다. 대부분의 백엔드 재작업은 잘못된 계약을 빨리 구축하는 데서 발생합니다.
- 지루한 것을 선호하세요. 영리한 해결책보다는 검증되고, 읽기 쉽고, 명백한 해결책을 선택하세요. 백엔드는 새벽 3시에 무인으로 실행됩니다; 우아함이 아니라 반쯤 잠든 상태에서도 이해해야 하는 온콜 엔지니어를 위해 최적화하세요.
아키텍처 — 규칙
- 의존성이 안쪽을 가리키도록 계층화하세요.
transport (http) → services (use-cases) → domain (entities/rules), 그리고services → repositories (I/O) → domain. 도메인은 바깥쪽을 임포트하지 않으며 I/O를 수행하지 않습니다. 임포트 화살표가 잘못된 방향을 가리키면 설계가 깨진 것입니다. - 핸들러는 조정하고, 서비스는 결정하며, 리포지토리는 I/O를 수행합니다. 핸들러는 입력을 파싱/검증하고, 서비스를 호출하며, 결과를 응답에 매핑합니다 — 비즈니스 로직은 없습니다. 리포지토리는 DB, 큐 또는 외부 HTTP를 건드리는 유일한 곳입니다.
- 인프라 없이 도메인을 테스트 가능하게 유지하세요. 데이터베이스나 네트워크 없이 핵심 로직을 단위 테스트할 수 없다면 로직이 잘못된 계층에 있는 것입니다.
- 하나의 컴포지션 루트. 단일 장소에서 구성, 의존성 와이어링, 서버 시작을 구축하세요. 그 외 모든 것은 의존성을 주입받습니다; 아무것도 전역 싱글톤을 생성하기 위해 밖으로 나가지 않습니다.
- 무상태로 설계하세요. 세션/스크래치 상태는 프로세스 메모리가 아닌 공유 저장소(DB/캐시)에 저장되므로 서비스가 수평 확장되고 재시작에도 살아남습니다. 모듈 수준 변수에 캐싱하고 있다면 멈추고 생각하세요.
→ 이러한 계층이 이미 구성된 스켈레톤을 구축하려면 api-service-scaffold를 사용하세요.
API 설계 — 규칙
- 명시적이고 버전이 지정된 계약. 첫날부터 API 버전을 지정하세요 (URL 또는 헤더); 출시된 계약에 호환되지 않는 변경을 절대 하지 마세요 — 새로운 형태를 추가하고 이전 것을 폐기하세요.
- 모든 입력을 경계에서 검증하여 타입이 지정된 값으로 변환하세요. 코어는 원시 요청 본문을 절대 보지 않습니다. 잘못된 입력은 422와 필드 수준 세부 정보로 거부하세요; 클라이언트를 절대 신뢰하지 마세요.
- 하나의 오류 모델, 한 번 매핑. 도메인 코드는 타입이 지정된 오류(
NotFoundError,ConflictError,ValidationError,UnauthorizedError…)를 발생시킵니다; 단일 경계 매퍼가 이를 상태 +{ code, message, details? }로 변환합니다. 스택 추적이나 내부 메시지를 절대 노출하지 마세요.404는 "찾을 수 없음" 그리고 "당신 것이 아님"에 사용합니다 — 이를 구분하면 존재 여부가 누출됩니다. - 일관된 형태. 성공에 동일한 엔벨로프, 오류에 동일한 엔벨로프, 일관된 페이지네이션(대용량 세트의 경우 오프셋보다 커서), 일관된 필드 케이싱. 호출자는 한 번만 관례를 배우면 됩니다.
- 안전하지 않은 작업에 대한 멱등성.
PUT/DELETE는 정의상 멱등적입니다; 재시도 가능한POST는 멱등성 키로 안전하게 만드세요. 네트워크는 재시도합니다 — 이중 청구된 고객은 설계 버그입니다. - 올바른 상태 코드. 2xx 성공, 4xx 호출자 실수, 5xx 당신 실수. 검증 실패는 422이지, 오류 본문이 있는 200이 아닙니다; 인증 실패는 401/403이지, 400이 아닙니다.
데이터 계층 — 규칙
- 데이터베이스는 진실의 원천입니다; 불변 조건을 DB에서 강제하세요. 외래 키, 고유 제약 조건,
NOT NULL, 검사는 스키마에 속하며 애플리케이션 코드에만 있지 않습니다 — 앱 코드에는 버그와 경합 조건이 있습니다; DB는 거짓말하지 않습니다. - 트랜잭션은 불변 조건을 감싸며 가능한 한 짧게 유지하세요. 모두 아니면 전무여야 하는 모든 것은 하나의 트랜잭션에 들어갑니다; 트랜잭션을 보유하는 동안 느린 작업(외부 호출, 무거운 계산)을 하지 마세요. 긴 트랜잭션은 잠금을 유지하고 다른 모든 사람을 굶깁니다.
- 동시성을 예상하세요. 두 요청이 동시에 같은 행에 도달합니다. 낙관적 잠금(버전 열) 또는 적절한 격리 수준을 사용하세요; 보호 없이 왕복에 걸쳐 읽기-수정-쓰기를 절대 하지 마세요.
- 마이그레이션은 하위 호환 가능하고 되돌릴 수 있어야 합니다. 한 번의 배포에서 열의 의미를 변경하지 마세요; 확장→마이그레이션→축소하세요. 핫 테이블에서 테이블 블로킹 잠금을 절대 획득하지 마세요. 모든 마이그레이션에는 테스트된 롤백이 함께 제공됩니다.
- N+1을 주의하세요. 관련 데이터를 쿼리 루프가 아닌 세트로 가져오세요. 실제 엔드포인트에서 쿼리 수를 프로파일링하세요, 지연 시간만이 아닙니다.
- 실제로 실행하는 쿼리에 인덱스를 생성하세요 — 그리고 그것들만. 사용되지 않는 인덱스는 아무것도 아닌 쓰기 증폭입니다.
→ 마이그레이션 메커니즘(잠금, 확장-축소, 백필, 롤백)에 대해서는 db-migration-guardian을 사용하세요.
보안 — 기준 (절대 변경 불가)
- 모든 요청에서 서버 측으로 인증 후 인가하세요. 클라이언트가 누구인지 또는 무엇을 할 수 있는지에 대한 주장을 절대 신뢰하지 마세요. 모든 리소스 접근에서 소유권을 확인하세요 — "유효한 토큰이 있습니다" ≠ "이 레코드는 내 것입니다".
- 모든 쿼리를 매개변수화하세요. 문자열로 빌드된 SQL은 절대 안 됩니다. 주입 가능한 인터페이스(셸, LDAP, 템플릿)에도 동일하게 적용됩니다.
- 비밀은 환경/비밀 관리자에서 가져오며, 코드에서 절대 가져오지 않습니다. 소스, 로그 또는 오류 메시지에 민감한 정보가 없어야 합니다. (
secrets-management스킬 참조.) - 모든 곳에서 최소 권한 — DB 사용자, 서비스 자격 증명, 클라우드 역할은 서비스에 필요한 정확한 범위로 제한됩니다.
- 인바운드 엔드포인트와 아웃바운드 호출에 속도 제한 및 타임아웃을 설정하세요. 무제한 호출 또는 제한 없는 엔드포인트는 발생할 준비가 된 서비스 거부 공격입니다.
- 경계에서 입력을 검증하고 인코딩하여 주입 및 SSRF를 방지하세요; 다른 내부 서비스를 포함한 모든 외부 입력을 신뢰할 수 없는 것으로 취급하세요.
동시성, 작업 및 장애
- 모든 외부 호출이 실패한다고 가정하세요. 타임아웃을 설정하고, 백오프 + 지터가 있는 제한된 재시도를 추가하고, 불안정한 의존성에 대한 차단기를 추가하세요. 타임아웃 없는 재시도는 하나의 느린 의존성이 전체 서비스를 중단시키는 방법입니다.
- 작업을 멱등적으로 만들어 재시도(귀하 또는 호출자의)가 안전하도록 하세요.
- 백그라운드 작업은 큐에 넣고, 요청 경로에 두지 마세요. 요청이 큐에 넣습니다; 작업자가 처리합니다; 실패는 조용한
catch {}가 아닌 데드 레터 큐에 도착하여 검사됩니다. - 크게 실패하고, 우아하게 저하하세요. 오류를 텔레메트리에 표시하세요; 중요하지 않은 의존성이 다운된 경우, 전체 요청을 500 처리하기보다 저하하세요(오래된 데이터 제공, 보강 건너뛰기).
관찰 가능성은 "완료"의 일부이며, 나중에 생각하는 것이 아닙니다
프로덕션에서 디버그할 수 없는 서비스는 완성된 것이 아닙니다. 구조화된 로그, RED/USE 메트릭, 하나의 상관관계 ID에 연결된 추적은 첫 번째 사고 이후가 아니라 구축하면서 넣으세요.
→ 계측 세부 사항 및 카디널리티/PII 보호 장치에 대해서는 backend-observability를 사용하세요.
테스트 입장
- 공개 표면(엔드포인트/유스케이스)을 통해 동작을 테스트하고, 비공개 내부는 테스트하지 마세요 — 그래야 테스트가 리팩터링에서 살아남습니다.
- 아이스크림 콘이 아닌 피라미드: 도메인/서비스 로직에 대한 많고 빠른 단위 테스트, 실제 DB와 와이어링에 대한 집중된 통합 테스트 세트, 중요 경로에 대한 소수의 엔드투엔드 테스트.
- 모든 버그 수정에는 수정 전에는 실패하고 수정 후에는 통과하는 회귀 테스트가 포함됩니다.
- 결정론적 테스트. 벽시계, 네트워크 또는 순서에 의존하지 마세요; 데이터를 명시적으로 시드하고; 테스트를 서로 격리하세요.
모든 백엔드 작업 절차
- 주변 코드를 읽고 계약을 확정하세요 (입력/출력/오류/호출자/일관성).
- 작업을 올바른 계층에 배치하세요; 도메인을 I/O 프리로 유지하세요.
- 경계에서 입력을 검증하세요; 하나의 오류 모델을 사용하세요; 올바른 상태를 반환하세요.
- DB에 불변 조건 + 짧은 트랜잭션을 넣으세요; 동시성을 처리하세요; 마이그레이션을 확장-축소로 계획하세요.
- 보안 기준을 적용하세요 (인가 + 소유권, 매개변수화된 쿼리, 비밀, 타임아웃/제한).
- 외부 호출 및 작업을 복원력 있고 멱등적으로 만드세요.
- 진행하면서 계측하세요; 올바른 계층에 테스트를 추가하고 버그에 대한 회귀 테스트를 포함하세요.
- 관련 특정 스킬의
완료 정의와 대조하여 확인하세요.
완료 정의
- 계층이 존중됨 (도메인은 I/O 없음; 의존성이 안쪽을 가리킴); 하나의 컴포지션 루트.
- 모든 입력이 경계에서 검증됨; 하나의 오류 모델이 한 번 매핑됨; 올바른 상태 코드; 버전이 지정된 계약.
- DB에서 불변 조건이 강제됨; 트랜잭션이 짧음; 동시성이 처리됨; 마이그레이션이 하위 호환 가능 + 되돌릴 수 있음.
- 보안 기준 충족: 인가 + 소유권 검사, 매개변수화된 쿼리, 외부 비밀, 타임아웃/속도 제한 존재.
- 외부 호출/작업이 멱등적이고 복원력 있음; 백그라운드 작업이 요청 경로에서 벗어남.
- 관찰 가능성 연결됨 (상관관계 ID에 대한 로그/메트릭/추적); 올바른 계층의 테스트, 통과, 회귀 테스트 포함.