Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Hiểu vì sao API cần versioning — breaking change (đổi schema, rename field, remove endpoint) không tránh được nhưng client deployed không thể update đồng thời server, cần coexist nhiều version song song.
- Biết 3 approach versioning: URL versioning
/api/v1, Header versioning qua vendor MIMEAccept: application/vnd.shop.v2+json, Query versioning?v=1. - Phân biệt vendor MIME type
application/vnd.<vendor>.<version>+jsontheo RFC 6838 với Accept header version đơn giản. - Đọc được decision matrix trade-off mỗi approach trên 8 chiều: visibility, cache, browser test, tooling, pure-REST, industry adoption, discovery, routing code.
- Biết industry pattern lock từ production: Stripe, GitHub, AWS, Twitter, Twilio.
- Confirm Shop API chọn URL versioning
/api/v1(lock từ B5 vendor MIME đã loại + B24 nest đã apply) và roadmap migration v1→v2 4 phase vớiDeprecation/Sunsetheader theo RFC 8594. - Biết folder structure khi có v2:
crates/shop-api/src/routes_v2/riêng giữ v1 stable. - Hoàn thành Group 3 Routing Cơ Bản (10/10 bài) — chuẩn bị bước vào Group 4 Extractors Và Response Sâu.
Tại Sao Cần API Versioning?
API là contract (cam kết) giữa server và client: server hứa response shape không đổi để client parse được. Khi API có user thật (mobile app, third-party integration, partner system), contract đó trở thành ràng buộc cứng — server không thể tùy tiện đổi.
Breaking change (thay đổi phá vỡ tương thích) không tránh được sau vài tháng/năm vận hành:
- Đổi schema response — vd field
pricetừi64cent sang{"amount": "12.50", "currency": "USD"}object để hỗ trợ multi-currency. - Rename field — vd
created→createdAttheo convention mới (camelCase + suffixAt). - Remove endpoint — vd
/api/v1/legacy/checkoutbị thay bằng/api/v1/checkoutflow mới. - Behavior change — vd
GET /productsmặc định trả 20 item giờ trả 50 item, hoặc thay đổi sort default. - Đổi auth requirement — endpoint public giờ cần JWT, hoặc đổi scope cần thiết.
Client deployed KHÔNG thể update đồng thời server — đây là vấn đề cốt lõi:
- Mobile app: user cài app phiên bản cũ trên iOS/Android, không tự cập nhật cho đến khi mở App Store/Play Store. Có user dùng app version 1.2 từ 2 năm trước.
- Third-party integration: đối tác tích hợp API từ năm ngoái, không có resource cập nhật ngay khi server đổi contract — có thể mất 3-6 tháng để partner deploy fix.
- IoT/embedded device: firmware update hiếm (mỗi 6-12 tháng), không thể đẩy breaking change lên server.
Anti-pattern phổ biến: deploy breaking change KHÔNG version → toàn bộ client cũ break cùng lúc, support team flood, partner mất lòng tin, app store bị 1-star review hàng loạt. Versioning là cơ chế kỹ thuật giải bài toán này — cho phép server expose nhiều version cùng lúc, mỗi client tự chọn version phù hợp tốc độ release của mình.
API có version cũng làm rõ chính sách deprecation (gỡ bỏ): server cam kết duy trì v1 ít nhất 12 tháng sau khi v2 ra, đủ thời gian để client migrate.
Approach 1: URL Versioning /api/v1
URL versioning là approach phổ biến nhất 2026 — prefix version vào path URL:
GET /api/v1/products → handler v1, schema cũ
GET /api/v2/products → handler v2, schema mới
POST /api/v1/checkout → handler v1 flow cũ
POST /api/v2/checkout → handler v2 flow mới
Pros:
- Visibility cao — dev đọc URL biết version ngay không cần xem header hay query. Log line
GET /api/v1/productstự nói version. - Cache CDN dễ — URL khác = cache key khác, không cần
Varyheader phức tạp; CloudFront/Cloudflare/Fastly cache theo URL mặc định. - Browser bookmark + share link + debug curl trivial — paste URL trình duyệt là test được endpoint GET, không cần set header.
- Tooling universal — Postman/Swagger UI/Insomnia/Bruno hiển thị version rõ trong sidebar collection.
- Routing code đơn giản — framework nào cũng support prefix routing (axum
nest, Expressapp.use('/api/v1', router), Spring@RequestMapping("/api/v1")). - Discovery dễ — client mới đọc doc thấy
https://api.shop.com/api/v1/productshiểu ngay version 1.
Cons:
- URL "không thuần REST" — purist theo Roy Fielding cho rằng resource URL không nên có version, vì version là thuộc tính representation chứ không phải resource. Trong thực tế tranh luận này ít có giá trị thực tiễn — Stripe/GitHub/AWS đều dùng URL versioning.
- Routing code có thể duplicate — nếu v2 chỉ khác v1 ở 2-3 endpoint, vẫn phải khai báo lại toàn bộ sub-router. Khắc phục bằng cách share handler v1 với những endpoint không đổi.
Shop API decision: dùng URL versioning, lock từ B5 (vendor MIME đã loại) và đã apply ở B24 qua Router::nest. Code router.rs hiện tại:
// File: crates/shop-api/src/router.rs (sau B25)
pub fn build_router(state: AppState) -> Router {
let api_v1 = Router::new()
.merge(routes::products::routes());
Router::new()
.route("/", get(root))
.merge(routes::health::routes())
.merge(routes::version::routes())
.nest("/api/v1", api_v1) // ← URL versioning prefix
.fallback(handlers::fallback::not_found)
.with_state(state)
}
Khi cần v2: thêm đúng 1 sub-router + 1 dòng nest() nữa, KHÔNG đụng v1.
Approach 2: Header Versioning (Vendor MIME)
Header versioning gửi version qua Accept header. Convention chuẩn là vendor MIME type theo RFC 6838: application/vnd.<vendor>.<version>+json — prefix vnd. nghĩa "vendor-specific" (không phải MIME chuẩn IANA).
# Request v1
curl -H "Accept: application/vnd.shop.v1+json" \
https://api.shop.com/products
# Request v2
curl -H "Accept: application/vnd.shop.v2+json" \
https://api.shop.com/products
# Server parse Accept → route đến handler v1 hoặc v2
URL /products giữ nguyên không phụ thuộc version — resource identity không đổi theo triết lý Roy Fielding.
Pros:
- URL stable — không cần
/v1,/v2trong path; resource identity giữ nguyên trọn lifecycle API. - "Pure REST" theo Fielding — version là thuộc tính của representation (Accept negotiate), không phải resource (URL).
- Resource discovery clean — Swagger spec mô tả URL ổn định, doc không đầy
/v1/....
Cons:
- Tooling pitfall — curl phải set
-H "Accept: ..."mỗi lần, Postman phải config header per request, Bruno phải khai báo trong env. Quên header → server fallback default version, dễ bug âm thầm. - Browser KHÔNG thể test trực tiếp — type URL vào address bar gửi
Accept: text/html, không phân biệt được version → dev không thể quick test GET endpoint trình duyệt. - Cache CDN phức tạp — cùng URL nhưng response khác theo Accept → cần
Vary: Acceptheader, CDN phải store nhiều variant per URL; hit rate giảm; CloudFront/Cloudflare cấu hình phức tạp hơn. - Discovery khó — không nhìn URL biết version đang gọi, phải xem doc hoặc inspect request header.
- Log analysis khó — log line
GET /productskhông nói version, phải log thêm Accept header để biết — tốn disk, parser phức tạp.
Industry use: GitHub REST API dùng Accept: application/vnd.github.v3+json kết hợp URL /api/v3 — hybrid pattern.
Shop API KHÔNG dùng vendor MIME — lock từ B5: tooling pitfall + browser test inconvenience + cache phức tạp quá nhiều cho lợi ích "pure REST" thuần lý thuyết. Team nhỏ ưu tiên visibility + tooling universal hơn purity.
Approach 3: Query Versioning ?v=1
Query versioning gửi version qua query string:
curl https://api.shop.com/products?v=1
curl https://api.shop.com/products?v=2
curl https://api.shop.com/products?api_version=2024-06-20
Pros:
- Simple cho client — chỉ thêm query parameter, không cần set header.
- Browser test được — paste URL với
?v=2vào address bar work. - Tooling support — curl/Postman handle query string mặc định.
Cons:
- URL không stable — cùng resource có 2 URL (
/products?v=1vs/products?v=2), bookmark/share link khó. - Mixed concern — query string thường dùng cho filter/pagination/sort (
?page=1&size=20), trộn version vào dễ confusing với business param. - Optional default ambiguous — không gửi
vthì server dùng version nào? Latest? Earliest? Mỗi lựa chọn đều có pitfall. - Cache CDN ambiguous — query khác = key khác mặc định, nhưng nếu CDN cấu hình strip query → cache nhầm version (anti-pattern phổ biến CloudFront default behavior).
- Routing code awkward — phải parse query trong handler để dispatch sang handler tương ứng version, không tách bạch ở router level.
Industry use: rất ít. Twitter API v1.1 trước đây có ?api_version nhưng đã loại khi chuyển sang Twitter v2 URL-based. Stripe có header Stripe-Version (custom header riêng, không phải query). Hiện 2026 không có API public lớn nào dùng query versioning làm primary.
Shop API KHÔNG dùng — mixed concern với filter/pagination là deal-breaker. Lock URL versioning từ B5.
Decision Matrix
Bảng so sánh 8 chiều giúp quyết định approach phù hợp:
Aspect │ URL `/v1` │ Header │ Query `?v=1`
────────────────────┼────────────────────┼────────────────────┼──────────────────
Visibility │ High │ Low │ Medium
Cache CDN │ Easy (URL key) │ Need Vary: Accept │ Easy if not strip
Browser test │ Yes │ No │ Yes
Tooling │ Universal │ Need header set │ Universal
"Pure REST" │ No (purist view) │ Yes (Fielding) │ Mixed concern
Industry adoption │ Stripe, AWS, │ GitHub (hybrid), │ Rare (Twitter
│ Twitter v2, Twilio │ HATEOAS demo │ legacy bỏ)
Discovery │ URL clear │ Schema doc │ URL + query
Routing code │ Duplicate per ver │ Branch in handler │ Branch in handler
Khuyến nghị:
- URL versioning cho hầu hết case — visibility cao + tooling universal + cache đơn giản thắng cho team < 50 người, project < 5 năm.
- Header versioning chỉ khi cần "pure REST" cho HATEOAS hypermedia API hoặc team tooling-ready (Postman collection auto-set header, CI test framework set sẵn).
- Query versioning không nên dùng — mixed concern + cache ambiguous + không industry pattern lớn nào dùng làm primary.
- Hybrid URL + custom header như Stripe (
/v1/...+Stripe-Version: 2024-06-20) cho fine-grained per-account version — pattern advanced, chỉ áp dụng khi cần backward compat per-merchant như payment processor.
Shop API chọn URL versioning thuần — đủ cho roadmap 3 năm tới, có thể bump v2 khi schema thay đổi major.
Industry Pattern: Stripe, GitHub, AWS
Phân tích pattern từ 5 production API lớn nhất 2026:
Provider │ Primary Versioning │ Secondary │ Pattern
─────────┼─────────────────────────┼─────────────────┼──────────────
Stripe │ URL /v1/charges │ Stripe-Version │ URL + custom
│ │ 2024-06-20 │ header hybrid
│ │ per-account │ date-based
GitHub │ URL /api/v3/repos │ Accept vnd. │ URL + vendor
│ │ github.v3+json │ MIME hybrid
AWS │ URL /2024-01-01/... │ — │ URL date-based
Twitter │ URL /2/tweets │ — │ URL numeric
Twilio │ URL /2010-04-01/ │ — │ URL date-based
│ Accounts │ │
Stripe dùng URL /v1/charges cho path đồng thời custom header Stripe-Version: 2024-06-20 per request hoặc per account. Dashboard cho merchant chọn account version mặc định, override per request qua header. Hybrid pattern advanced cho payment processor cần fine-grained backward compat — không khuyến nghị cho team mới.
GitHub REST API dùng URL /api/v3/repos nhưng cũng chấp nhận Accept: application/vnd.github.v3+json để client explicit yêu cầu version. Header có thể chỉ định media type cụ thể như application/vnd.github.v3.raw cho raw content hoặc .html cho rendered HTML — combine versioning với content negotiation.
AWS dùng date-based version /2024-01-01/... trong URL — mỗi service có endpoint riêng https://<service>.<region>.amazonaws.com/2024-01-01/.... Date format ISO 8601 nói rõ "version of API as of this date" — dễ chọn version stable.
Twitter API v2 dùng URL numeric /2/tweets sau khi loại bỏ query ?api_version của v1.1. Migration công khai (deprecation notice 12 tháng) trở thành case study đáng học.
Twilio dùng URL date-based /2010-04-01/Accounts — version từ năm 2010 vẫn maintain để client legacy không break. Cam kết backward compat dài hạn của Twilio là điểm bán hàng cho enterprise.
Pattern phổ biến chung: URL versioning là primary cho mọi public API lớn. Header (vendor MIME hay custom) chỉ là secondary cho hybrid pattern advanced. Query versioning hầu như đã biến mất khỏi production API 2026.
Shop API Roadmap Versioning
State hiện tại: Shop API đang ở v1, mọi resource mount dưới prefix /api/v1 qua nest("/api/v1", api_v1) trong build_router() (lock B24). URL effective: /api/v1/products, /api/v1/cart, /api/v1/orders, v.v.
Khi nào bump v2? Chỉ khi có breaking change không thể avoid:
- Đổi schema response major — vd
pricetừ cent integer sangMoneyobject multi-currency. - Remove field/endpoint không backward-compat — vd loại bỏ field
legacy_idđã deprecated 12 tháng. - Đổi behavior default — vd
GET /productsđổi sort default từcreatedAtsangrelevance. - Đổi auth scope — endpoint cũ public giờ cần JWT, hoặc đổi required scope.
Non-breaking change KHÔNG bump version — thêm field mới optional, thêm endpoint mới, thêm query param optional đều backward-compat với client cũ. Chỉ bump v2 cho breaking change thật.
Migration strategy 4 phase lock cho Shop API:
Phase 1: Deploy v2 song song v1
- Tạo folder routes_v2/ riêng
- Thêm nest("/api/v2", api_v2) vào router.rs
- v1 vẫn 100% như cũ, KHÔNG sửa
- Test v2 internal, không announce public
Phase 2: Public announce v2
- Update OpenAPI doc (B8) với cả v1 + v2 spec
- Mark v1 endpoint với "deprecated: true" trong OpenAPI
- Blog post / email partner announce v2 ready
- Khuyến khích client mới integrate v2
Phase 3: Add Deprecation/Sunset header v1
- Response v1 thêm header per RFC 8594:
Deprecation: true
Sunset: Wed, 01 Jan 2027 00:00:00 GMT
Link: <https://docs.shop.com/v2-migration>;
rel="deprecation"
- Client phát hiện header, plan migrate
Phase 4: Gradually shutdown v1
- Monitor metrics: % traffic v1 vs v2 mỗi tuần
- Email client còn dùng v1 (qua API key tracking)
- Khi v1 traffic dưới 5%, set sunset date final
- Sunset: trả 410 Gone với link migration doc
Code preview khi có v2:
// File: crates/shop-api/src/router.rs (preview khi có v2)
pub fn build_router(state: AppState) -> Router {
let api_v1 = Router::new()
.merge(routes::products::routes()); // v1 schema cũ giữ nguyên
let api_v2 = Router::new()
.merge(routes_v2::products::routes()); // v2 schema mới
Router::new()
.merge(routes::health::routes())
.merge(routes::version::routes())
.nest("/api/v1", api_v1) // v1 vẫn serve
.nest("/api/v2", api_v2) // v2 song song
.fallback(handlers::fallback::not_found)
.with_state(state)
}
Folder structure khi có v2 — lock pattern: tạo folder routes_v2/ riêng, KHÔNG sửa file routes/ v1 cũ:
crates/shop-api/src/
├── routes/ ← v1 stable, KHÔNG sửa
│ ├── mod.rs
│ ├── products.rs
│ ├── cart.rs
│ └── ...
├── routes_v2/ ← v2 mới
│ ├── mod.rs
│ ├── products.rs ← schema mới
│ └── ...
├── handlers/
├── dto/ ← v1 DTO giữ nguyên
├── dto_v2/ ← v2 DTO mới (nếu schema khác)
└── router.rs ← nest cả v1 và v2
Lý do tách folder riêng thay vì branch if version == 2 trong handler:
- Cô lập rủi ro — sửa v2 không thể accidentally break v1.
- Code review dễ — diff v2 chỉ trong
routes_v2/, reviewer không cần check v1 có bị đụng không. - Test riêng — test suite v1 + v2 tách bạch, regression riêng.
- Delete v1 dễ khi sunset — chỉ cần xóa folder
routes/+ dòngnest("/api/v1", ...), không phải refactor nhánh logic phức tạp.
RFC 8594 (Sunset HTTP Header Field, 2019) chuẩn hóa Sunset header với date format RFC 7231 (HTTP-date). RFC mới hơn cho Deprecation header (draft IETF) đang được nhiều API adopt — Shop API lock cả 2 header cho phase 3.
Tổng Kết
- API versioning cần để coexist nhiều client version (mobile app, third-party integration, IoT device) không thể update đồng thời server — versioning là contract giữa server và client.
- Breaking change không tránh được: đổi schema, rename field, remove endpoint, behavior change, đổi auth scope — anti-pattern deploy breaking không version → break toàn client cùng lúc.
- 3 approach: URL
/api/v1(visibility cao + tooling universal), Header vendor MIMEAccept: application/vnd.shop.v2+json(pure REST, URL stable), Query?v=1(rare, mixed concern). - Decision matrix 8 chiều: URL thắng visibility/cache/browser/tooling/discovery; Header thắng pure-REST/URL-stable; Query không thắng chiều nào quan trọng.
- Industry pattern 2026: Stripe (URL + Stripe-Version hybrid), GitHub (URL + vendor MIME hybrid), AWS (URL date-based), Twitter v2 (URL numeric), Twilio (URL date-based) — đều dùng URL làm primary.
- Shop API lock URL versioning
/api/v1— confirm từ B5 (vendor MIME loại) + B24 (nest("/api/v1", api_v1)apply); đổi v1→v2 chỉ 1 dòngnest()thêm. - Migration v1→v2 lock 4 phase: (1) deploy song song
routes_v2/, (2) OpenAPI document v1 deprecated, (3) headerDeprecation: true+Sunset: <RFC 7231 date>theo RFC 8594, (4) monitor metrics + email client + gradually shutdown. - Folder structure khi có v2:
crates/shop-api/src/routes_v2/riêng giữ v1 stable — cô lập rủi ro, code review dễ, test riêng, delete v1 trivial khi sunset. - Non-breaking change (thêm field optional, thêm endpoint, thêm query param) KHÔNG bump version — backward-compat với client cũ.
- HOÀN THÀNH Group 3 Routing Cơ Bản (10/10 bài) — sẵn sàng vào Group 4 Extractors Và Response Sâu từ B31.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Tại sao API cần versioning? Mô tả anti-pattern nếu deploy breaking change KHÔNG version, và hệ quả cho client mobile/third-party.
- 3 approach versioning. Approach nào visibility cao nhất? Tại sao log line + browser test + discovery đều dễ hơn?
- Header versioning với
Accept: application/vnd.shop.v2+json. Liệt kê 3 tooling pitfall khi dùng vendor MIME trong production. - Shop API chọn URL versioning
/api/v1. Industry pattern nào tương tự (chỉ 3 ví dụ)? Pattern hybrid của Stripe khác gì pattern thuần URL của Twitter v2? - Migration v1→v2 lock 4 phase. Liệt kê ngắn gọn từng phase + nêu RFC nào chuẩn hóa
Sunsetheader.
Đáp án
- API versioning cần vì API là contract giữa server và client — server hứa response shape ổn định để client parse được. Breaking change (đổi schema, rename field, remove endpoint, đổi behavior default, đổi auth scope) không tránh được sau vài tháng/năm vận hành. Client deployed KHÔNG thể update đồng thời server: mobile app user cài bản 1.2 từ 2 năm trước không tự cập nhật cho đến khi mở App Store, third-party integration partner cần 3-6 tháng để deploy fix sau khi server đổi contract, IoT device firmware update hiếm (mỗi 6-12 tháng). Anti-pattern deploy breaking change KHÔNG version → toàn bộ client cũ break cùng lúc, hệ quả: support team flood ticket "app không hoạt động", partner mất lòng tin có thể chuyển sang competitor, mobile app bị 1-star review hàng loạt trong 24-48h, doanh thu tụt do user không complete checkout, SLA contract với enterprise partner bị vi phạm có thể penalty financial. Versioning cho phép server expose nhiều version cùng lúc, mỗi client tự chọn version phù hợp tốc độ release của mình, server cam kết duy trì v1 ít nhất 12 tháng sau khi v2 ra đời.
- URL versioning visibility cao nhất. Đọc URL
/api/v1/productsbiết ngay version 1, không cần xem header hay query. Cụ thể 4 chiều: Log analysis — log lineGET /api/v1/products 200 142mstự nói version, grep/v1/ra mọi request v1 trong 1 giây, không cần parse Accept header tốn CPU. Browser test — pastehttps://api.shop.com/api/v1/productsvào address bar Chrome/Firefox work ngay với GET endpoint, dev quick verify không cần Postman. Discovery — client mới đọc doc thấy URL có/v1/hiểu ngay version, không cần đọc thêm section "How to specify version". Tooling universal — Postman/Swagger UI/Insomnia/Bruno/curl/HTTPie/wget mọi tool hiển thị URL với version rõ ràng trong UI/CLI, không tool nào cần config đặc biệt để "show version". Share link — gửi URL cho colleague debug bug, version inherent trong link, không cần kèm note "remember to set Accept header v1". Đây là lý do Stripe/GitHub/AWS/Twitter/Twilio đều dùng URL làm primary. - 3 tooling pitfall vendor MIME: (a) curl phải set
-Hmỗi lần — command verbosecurl -H "Accept: application/vnd.shop.v2+json" https://api.shop.com/products, quên header → server fallback default version (thường latest), client cũ test bằng curl mà không set header sẽ nhận response v2 không parse được → debug khó vì curl không sai mà server "đúng" theo logic fallback. (b) Browser KHÔNG thể test trực tiếp — type URL vào address bar Chrome gửiAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8, không có vendor MIME → server không biết client muốn version nào → fallback default, dev không thể quick verify GET endpoint trong trình duyệt như URL versioning, phải mở Postman/curl cho mỗi test nhỏ → developer experience kém. (c) Postman/Bruno phải config header per request hoặc per collection — collection level set header thì tất cả request inherit; nếu dev có 2 collection (v1 + v2) phải maintain song song; nếu set per request thì 60 endpoint = 60 lần copy paste header; quên 1 request → bug âm thầm khó debug; export collection share team thiếu header config → đồng đội thiếu context. Hệ quả tổng: tooling friction tăng support cost, dev frustration cao, onboarding member mới tốn thời gian giải thích "tại sao curl bình thường không work". GitHub khắc phục bằng cách chấp nhận CẢ URL/api/v3CẢ vendor MIME (hybrid) để tooling-naive vẫn work, nhưng đó là Shop API tránh được hoàn toàn nếu chọn URL ngay từ đầu. - Industry pattern URL versioning tương tự Shop API: (a) Twitter API v2 dùng
/2/tweetsURL numeric thuần (sau khi loại bỏ query?api_versioncủa v1.1) — exactly pattern Shop API, đơn giản nhất, không hybrid. (b) AWS dùng URL date-based/2024-01-01/...— version là date snapshot, mỗi service endpoint riênghttps://<service>.<region>.amazonaws.com/2024-01-01/..., ưu điểm date format ISO 8601 nói rõ "version of API as of this date" dễ chọn version stable cho enterprise. (c) Twilio dùng URL date-based/2010-04-01/Accounts— version 2010 vẫn maintain để legacy client không break, cam kết backward compat dài hạn là điểm bán hàng cho enterprise. Stripe hybrid khác Twitter v2 thuần URL: Stripe có URL/v1/chargesđồng thời custom headerStripe-Version: 2024-06-20per request hoặc per account. Dashboard Stripe cho merchant chọn account version mặc định, override per request qua header — pattern hybrid này cho phép fine-grained version per merchant, không phải "tất cả client cùng v1 hay tất cả cùng v2". Use case: merchant A integrate API 3 năm trước trên version 2021-06-20, merchant B mới onboard dùng version 2024-06-20 — server serve cả hai version trên cùng URL/v1/charges, phân biệt quaStripe-Versionheader. Twitter v2 thuần URL không support fine-grained này — client phải chuyển sang URL v3 đồng loạt khi server bump. Trade-off Stripe hybrid: complexity cao (server phải maintain N version code path, version matrix khó test), lợi ích cho payment processor cần backward compat dài hạn per-merchant; KHÔNG khuyến nghị cho team mới, Shop API giữ pattern thuần URL như Twitter v2 đến khi cần thiết. - 4 phase migration: (a) Phase 1 - Deploy v2 song song v1: tạo folder
routes_v2/riêng (giữroutes/v1 stable), thêm dòngnest("/api/v2", api_v2)vàorouter.rs, v1 vẫn 100% không sửa, test v2 internal chưa announce public. (b) Phase 2 - Public announce v2: update OpenAPI doc (B8) với cả v1 + v2 spec, mark v1 endpoint với"deprecated": truetrong OpenAPI metadata, blog post hoặc email partner announce v2 ready, khuyến khích client mới integrate v2 ngay từ đầu. (c) Phase 3 - Add Deprecation/Sunset header v1: response v1 thêm 3 header —Deprecation: true(IETF draft),Sunset: Wed, 01 Jan 2027 00:00:00 GMT(date format RFC 7231 HTTP-date),Link: <https://docs.shop.com/v2-migration>; rel="deprecation"để client tự discover doc migration; client SDK well-written sẽ detect header tự log warning hoặc throw exception report đến monitoring, plan migrate; period giữa phase 3 và phase 4 nên ít nhất 12 tháng cho enterprise client. (d) Phase 4 - Gradually shutdown v1: monitor metrics % traffic v1 vs v2 mỗi tuần qua Prometheus dashboard, email client còn dùng v1 (qua API key tracking + customer success team), khi v1 traffic dưới 5% set sunset date final cứng, đến sunset trả 410 Gone với body link migration doc + status code 410 chứ không phải 404 (410 = intentionally removed, 404 = not found ambiguous), Cloudflare/CDN cũng update rule reject request v1. RFC chuẩn hóaSunsetheader: RFC 8594 (Sunset HTTP Header Field, published November 2019 bởi Erik Wilde) — định nghĩaSunsetheader với date format theo RFC 7231 mục 7.1.1.1 HTTP-date (format IMF-fixdateWed, 01 Jan 2027 00:00:00 GMT), khuyến khích kèmLinkheader vớirel="sunset"hoặcrel="deprecation"trỏ đến doc migration.Deprecationheader riêng đang ở IETF draft (Deprecation HTTP Header Field) chưa thành RFC nhưng nhiều API đã adopt — Shop API lock cả hai header cho phase 3.
Bài Tiếp Theo
Bài 31: Extractor Trait — Bản Chất — bắt đầu Group 4 Extractors Và Response Sâu: trait FromRequestParts + FromRequest, axum chạy mỗi extractor tuần tự, fail nhanh khi 1 fail, order matters (body extractor cuối). Chuẩn bị tạo custom extractor cho Shop API.
