유닛 하나가 지키는 것
유닛은 기능이 달라도 쓰는 법이 같아야 합니다. 새 유닛을 처음 쓰는 사람이 README 를 다 읽지 않아도 실패를 어떻게 다룰지 알 수 있게, 순서와 실패의 말과 의존 방향을 고정했습니다.
순서: 정규화, 로컬 검증, 캐시, 외부 호출
- 1
정규화
하이픈·공백을 걷고 형식을 맞춘다
- 2
로컬 검증
검증 숫자처럼 밖에 묻지 않고 알 수 있는 것
- 3
캐시
같은 값을 짧은 시간에 다시 묻지 않는다
- 4
외부 호출
타임아웃·재시도·오류 분류가 붙은 한 번
공개 함수는 이 순서를 바꾸지 않습니다. 어댑터를 가짜로 바꿔도 순서는 같아서, 개발에서 본 동작이 운영에서도 같습니다. 각 단계는 onStep 으로 기록을 내보냅니다. 동작에는 영향이 없고, 데모 화면과 로그가 씁니다.
실패는 여섯 가지 말로만
나누는 기준은 원인이 아니라 호출한 쪽이 다음에 할 일입니다. 유닛이 던지지 않고 결과(Result)로 돌려주므로, 타입이 실패 분기를 빠뜨리지 못하게 합니다.
| 종류 | 뜻 | 다시 보내면 |
|---|---|---|
invalid-input | 입력을 고쳐야 한다 | 같다 |
auth | 키·권한을 고쳐야 한다(운영자의 일) | 같다 |
quota | 한도에 걸렸다 | 기다리면 풀린다 |
timeout | 응답이 늦었다 | 달라질 수 있다 |
upstream-down | 상대 서버가 실패했다 | 달라질 수 있다 |
unexpected | 응답을 해석하지 못했다(명세가 바뀌었을 수 있다) | 같다. 사람이 본다 |
「없음」은 실패가 아닙니다. 등록되지 않은 사업자번호는 조회에 성공한 결과이고, 상태로 말합니다.
의존 방향
소비 앱 ─→ @duolabs-deux/unit-<id> ─→ unit-runtime ─→ unit-contracts
| 패키지 | 맡는 것 |
|---|---|
unit-contracts | Result, 오류 여섯 가지, manifest 타입. 코드가 거의 없다 |
unit-runtime | 외부 호출(타임아웃 기본 5초, 상태 코드·본문 분류, 재시도 기본 0회, Retry-After), 캐시, 카운터, 차단기 |
unit-<id> | 기능 하나. 어댑터(실제·가짜)와 파이프라인 |
유닛끼리는 서로 import 하지 않습니다. 둘을 엮는 것은 소비 앱입니다. CI 의 계약 검사가 이것을 막습니다.
어댑터: 실제 하나, 가짜 하나
가짜 어댑터는 키 없이 결정적으로 답합니다. 같은 입력에는 언제나 같은 답이라 테스트 번호를 사람이 만들 수 있습니다. 가짜의 답에는 출처 표시가 붙어서 화면에서 실제 조회처럼 보이지 않습니다.
밖에 묻지 않는 유닛(요청자 IP 처럼 입력만으로 답하는 것)은 어댑터가 없습니다. 입력이 같으면 답이 같으니 가짜가 곧 실물입니다.
재시도는 다시 보내도 안전한 요청(조회)에만 켭니다. 발송과 결제는 0회입니다. 인증키는 어댑터 옵션으로 받고, 유닛이 환경변수를 직접 읽지 않습니다. 어느 이름으로 둘지는 소비 앱이 정합니다.
소비 앱에서 쓰기
DEUX 를 이미 쓰는 프로젝트라면 1번은 되어 있습니다.
# 1. .npmrc (GitHub Packages, read:packages 토큰)
@duolabs-deux:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
# 2. 설치
npm install @duolabs-deux/unit-biz-registry
# 3. next.config.ts (TS 소스를 그대로 발행하므로 앱이 컴파일한다)
transpilePackages: ["@duolabs-deux/unit-contracts", "@duolabs-deux/unit-runtime", "@duolabs-deux/unit-biz-registry"]