통합과 배포 · 말없이 바뀐 계약 · 실습
필드 하나가 이름을 바꿨는데 합계만 조용히 틀렸다
목표
파트너 주문 API 의 1.3 과 1.4 를 나란히 놓고 무엇이 우리를 깨뜨리는지 필드 단위로 가린 뒤, 우리가 읽는 것만 적은 소비자 계약과 그 계약을 매번 확인하는 계약 시험을 만든다. 구판과 신판을 동시에 받아들이는 읽기 계층까지 붙인다.
왜 중요한가
연결이 끊기는 사고는 경보가 울린다. 필드가 이름을 바꾸는 사고는 울리지 않는다. rec.get("region") 은 예외 대신 None 을 돌려주고, 늘어난 열거값은 우리 분기 어디에도 안 들어가고, 정수가 십진 문자열이 되면 합계가 조용히 달라진다.
그래서 통합의 안전장치는 "문서를 잘 읽는 것" 이 아니라 기계가 매번 대조하는 계약이다. 계약에는 파트너의 전체 스키마가 아니라 우리가 실제로 읽는 필드만 적는다. 전부 적으면 우리가 안 쓰는 필드의 변경에도 빨간불이 켜지고, 소음이 늘면 사람은 시험을 끈다.
판올림은 한 번에 끝나지도 않는다. 구판과 신판이 몇 달 함께 도는 동안 읽는 쪽이 둘 다 받아들여야 하므로, 이름이 바뀐 자리는 별칭 표로 타입이 바뀐 자리는 정규화 함수로 한곳에 모은다.
채점기는 여러분의 문장을 믿지 않는다. 여러분이 만든 파트너 서버를 채점기가 고른 포트에 직접 띄워 응답을 받아 보고, 여러분의 판정기와 대조기를 채점기가 만든 입력으로 다시 실행해 답을 맞춰 본다.
단계
1. /root/contract/partner.py 를 만들어 포트 8011 에 띄우고, 두 판의 응답을 /root/contract/v13.json 과 /root/contract/v14.json 에 저장하세요.
2. 두 응답의 필드 차이를 /root/contract/diff.json 에 added·removed·type_changed·enum_added 네 칸으로 적으세요.
3. /root/contract/breaking.py 를 만들어 변경 한 건을 받아 우리를 깨뜨리는지 판정하게 하세요.
4. 우리가 읽는 필드만 적은 소비자 계약을 /root/contract/order.contract.json 에 쓰세요.
5. /root/contract/validate.py 를 만들어 계약과 레코드를 대조하고 위반을 missing·type·enum 으로 갈라 적게 하세요.
6. /root/contract/read_order.py 를 만들어 1.3 과 1.4 응답을 모두 같은 내부 모양으로 옮기세요.
7. /root/contract/contract_test.sh 를 만들어 파트너의 지금 응답을 계약과 대조하고, 어긋나면 0 이 아닌 코드로 끝나게 하세요.
8. /root/contract/contract_report.md 에 네 절로 보고하세요.
참고
- 파트너 서버 실행 계약:
python3 /root/contract/partner.py --port <포트> [--drift]./health는{"ok": true, "versions": ["1.3", "1.4"]},/v1.3/orders와/v1.4/orders는{"version": ..., "orders": [...]}를 냅니다. 주문은 24건입니다. - 두 판의 차이는 이렇습니다. 1.3 은
order_idamount(정수)currencystatusregionupdated_at을, 1.4 는order_idamount(십진 문자열)currencystatusmarketchannelupdated_at을 냅니다. 1.4 의status에는on_hold가 더 있습니다. --drift는 파트너가 공지 없이 또 움직인 판입니다. 7번 단계의 계약 시험이 이것을 0 이 아닌 코드로 걸러야 합니다.- 판정기 실행 계약:
python3 breaking.py --change <파일>은{"breaking": true|false, "reason": "..."}를 냅니다. 변경 JSON 은{"where": "response"|"request", "kind": "...", "field": "..."}이고 kind 는 add_field · remove_field · rename_field · type_change · add_enum_value · field_becomes_optional · add_optional_field · add_required_field · relax_required 아홉 가지입니다. 같은 kind 이름이 응답 쪽과 요청 쪽에 모두 나올 수 있고, 그때 답은 서로 다릅니다. 표에 없는 kind 가 오면 안전한 쪽이 아니라 깨진다고 답합니다. - 대조기 실행 계약:
python3 validate.py --contract <파일> --records <파일>은{"records": n, "ok": n, "violations": [{"index": i, "field": f, "kind": k}]}를 냅니다. kind 는 missing · type · enum 입니다. 타입 이름은 string · integer · decimal_string 셋입니다.ok는 위반이 하나도 없는 레코드의 수입니다. - 읽기 계층 실행 계약:
python3 read_order.py --in <응답 파일>은 정규화 레코드의 리스트를 냅니다. 각 레코드는order_idamount_krw(정수)currencystatusmarket다섯 칸입니다. - 계약 시험 실행 계약:
bash contract_test.sh <BASE_URL>은 위반이 없으면 0, 있으면 1 로 끝납니다. - 이 실습의 판정 규칙: 응답에서 필드가 더해지는 것만 안전하고 나머지는 깨뜨린다고 봅니다. 요청 쪽은 필수 필드가 더해지는 것만 깨뜨립니다. 이것은 소비자 관점의 규칙이며 RFC 가 정한 것이 아닙니다.
- 흔한 실수: 계약에 파트너의 모든 필드를 적기(우리가 안 쓰는 변경에도 빨간불이 켜집니다), 금액을 float 로 정규화하기(반올림이 생깁니다), 파트너 서버를 앞단에서 띄워 터미널이 막히기.
- 서버는 백그라운드로 띄우고
curl -sf http://127.0.0.1:8011/health가 될 때까지 기다린 뒤 다음으로 갑니다. 채점기는 여러분이 띄워 둔 프로세스를 보지 않고 스크립트를 직접 다시 띄웁니다.
단계 8개
- 두 판을 함께 내주는 파트너 띄우기
- 두 판의 차이를 기계가 읽게 뽑기
- 무엇이 우리를 깨뜨리는지 가리기
- 우리가 읽는 것만 적은 계약
- 계약과 실제 응답을 대조하기
- 구판과 신판을 함께 받아들이기
- 공지 없이 또 움직인 파트너 잡아내기
- 판올림 점검 보고서