외부 API 를 호출하는 서비스는 대상 URL · 엔드포인트 · 인증 정보 · 재시도 정책을 설정으로 빼 둔다. 포맷은 사람이 얼마나 자주 손대는지, 주석이 필요한지, 어떤 언어에서 읽는지로 정한다.
| 포맷 | 장점 | 단점 |
|---|---|---|
| YAML | 읽기 쉽다. 주석을 쓸 수 있다. 계층 표현이 자연스럽다 | 들여쓰기에 민감하다. 파서에 따라 해석이 갈리는 값이 있다 |
| TOML | 문법이 엄격해 모호함이 적다. 파이썬 3.11 부터 tomllib 로 표준 지원 | 깊은 계층이 장황해진다 |
| JSON | 어디서나 읽힌다. 기계가 생성하기 쉽다 | 주석을 쓸 수 없다. 사람이 쓰기 번거롭다 |
| XML | 스키마로 검증할 수 있다 | 장황하다. 새로 시작하는 서비스에서 고를 이유가 적다 |
사람이 편집하는 설정은 YAML 이나 TOML, 프로그램이 주고받는 설정은 JSON 이 무난하다. 여러 포맷을 섞지 말고 하나로 통일한다.
api:
base_url: "https://api.example.com/v1"
timeout_seconds: 10
endpoints:
user_info: "/user/info"
send_message: "/message/send"
auth:
type: bearer
token_env: NOTI_API_TOKEN # 값이 아니라 환경 변수 이름을 적는다
retry:
max_attempts: 3
backoff_seconds: 2
retry_on_status: [429, 500, 502, 503, 504]
log:
level: INFO
따옴표 없이 쓴 값이 의도와 다르게 해석되는 경우가 있다.
country: NO # YAML 1.1 파서에서는 불리언 false 가 된다
version: 1.10 # 문자열이 아니라 숫자 1.1 이 된다
port: 08080 # 0 으로 시작하는 값을 8진수로 읽는 파서가 있다
token: ${MASKED} # 숫자로 읽힌다
문자열이어야 하는 값은 따옴표로 감싼다. PyYAML 은 YAML 1.1 을 따르므로 yes · no · on · off 도 불리언이 된다.
파싱은 반드시 safe_load 로 한다. yaml.load 는 임의 객체를 만들 수 있어 설정 파일을 신뢰할 수 없는 경우 코드 실행으로 이어진다.
import yaml
with open("config.yaml", encoding="utf-8") as f:
cfg = yaml.safe_load(f)
토큰과 비밀번호는 별도 경로로 넣고 설정 파일에는 어디서 읽을지만 적는다. 설정 파일은 대개 저장소에 들어가고, 값이 함께 커밋되면 이력에서 지우기 어렵다.
import os
token = ${MASKED}"auth"]["token_env"]]
컨테이너라면 시크릿을 파일로 마운트하고 경로를 적는 방식이 환경 변수보다 낫다. 환경 변수는 프로세스 환경 조회와 코어 덤프로 새어 나간다.
auth:
type: bearer
token_file: /run/secrets/noti_api_token
설정 항목이 빠졌을 때 무엇이 되는지 코드에 명시한다. 없는 키를 그대로 참조하면 기동 시점이 아니라 그 코드에 도달할 때 터진다.
DEFAULTS = {
"api": {"timeout_seconds": 10},
"retry": {"max_attempts": 3, "backoff_seconds": 2},
"log": {"level": "INFO"},
}
구조 검증은 Pydantic 같은 도구로 기동 시점에 한 번에 한다. 틀린 설정으로 뜬 서비스보다 뜨지 않는 서비스가 낫다.
from pydantic import BaseModel, HttpUrl
class ApiConfig(BaseModel):
base_url: HttpUrl
timeout_seconds: int = 10
class Config(BaseModel):
api: ApiConfig
같은 파일에 운영과 개발을 함께 넣고 분기하면 실수로 반대쪽을 가리키기 쉽다. 공통 파일과 환경별 파일을 두고 겹쳐 읽는 방식이 안전하다.
config/
├── base.yaml
├── dev.yaml
└── prod.yaml
NOTI_ENV=prod ./run.sh