Bỏ qua và tới nội dung chính
Thiết kế contract và DSL

HTTP contract nên đủ đến mức nào để tạo ra code có thể dùng?

Một HTTP contract tốt không phải là tài liệu dài cho đủ ý, mà là bản mô tả đủ rõ để đội thi công tạo ra code dùng được, ít bất ngờ và dễ kiểm soát thay đổi. Vấn đề cốt lõi là xác định đúng phạm vi của contract, tách các lớp mô tả và buộc những phần quan trọng phải có cấu trúc.

Huỳnh Kim Đạt Huỳnh Kim Đạt
2 lượt xem 8 phút đọc
HTTP contract nên đủ đến mức nào để tạo ra code có thể dùng?

TL;DR

HTTP contract chỉ nên đủ sâu ở những phần quyết định hành vi giao tiếp và khả năng thi công: endpoint, schema, lỗi, quyền, trạng thái và liên hệ với workflow hoặc policy. Nó không nên ôm toàn bộ chi tiết nội bộ, cũng không nên mơ hồ đến mức đội triển khai phải tự đoán.

Key Takeaways

  • HTTP contract cần đủ cấu trúc để đội thi công triển khai, kiểm thử và sinh code phần lặp lại được.
  • Nên tách rõ domain, app, rule, workflow, policy và API contract thay vì dồn mọi thứ vào một tài liệu.
  • Phần bắt buộc phải chặt là schema, validation, error model, auth, authorization và state transition liên quan đến API.
  • Contract quá mỏng gây suy đoán, contract quá dày biến thành giấy tờ khó giữ đồng bộ với thực tế.
  • Contract tốt làm cho đầu ra ít bất ngờ hơn nhờ khóa phạm vi, ngôn ngữ và traceability.

Khi làm contract-first, câu hỏi quan trọng không phải là viết HTTP contract dài đến đâu, mà là viết đến mức nào để code sinh ra hoặc code triển khai sau đó thực sự dùng được. Nếu contract quá mỏng, đội thi công phải tự đoán và mỗi người đoán một kiểu. Nếu contract quá dày, nó nhanh chóng biến thành giấy tờ nặng nề, khó giữ đồng bộ với thực tế. Một HTTP contract tốt là contract làm cho đầu ra ít bất ngờ hơn, thay vì cố thay code bằng tài liệu.

Trong cách tiếp cận của Midi Coder, contract coding hiệu quả đến từ việc tách rõ lớp nào mô tả điều gì. HTTP contract chỉ là một lớp trong hệ thống contract lớn hơn. Nó cần đủ chặt để software factory có thể triển khai, kiểm thử, kiểm soát traceability và sinh ra những phần code lặp lại được, nhưng không nên ôm thay phần domain, workflow hay policy nếu các lớp đó đã có chỗ đứng riêng.

HTTP contract thực sự dùng để làm gì

HTTP contract mô tả bề mặt tương tác giữa client và hệ thống. Nó trả lời những câu hỏi như có endpoint nào, input và output có hình dạng gì, lỗi trả về ra sao, quyền truy cập được kiểm soát thế nào, hành vi đồng bộ và bất đồng bộ khác nhau ở đâu. Mục tiêu của nó là làm cho giao tiếp qua API đủ rõ để có thể triển khai, kiểm thử tự động, sinh client hoặc server stub, và giảm tranh cãi ở giai đoạn thi công.

Điều HTTP contract không nên cố gánh hết là toàn bộ tư duy nghiệp vụ nội bộ, chi tiết thuật toán, cấu trúc module trong code, hay mọi biến thể tương lai chưa được quyết định. Khi contract ôm quá nhiều thứ, nó vừa khó đọc vừa dễ lỗi thời.

Các lớp contract cốt lõi cần tách ra

  • Domain contract: định nghĩa ngôn ngữ nghiệp vụ, thực thể, khái niệm, trạng thái và thuật ngữ chuẩn. Đây là nơi quyết định từ nào được dùng và dùng theo nghĩa nào.
  • App contract: xác định năng lực ứng dụng cần có, module nào chịu trách nhiệm gì, biên giới hệ thống nằm ở đâu.
  • Rule contract: mô tả các quy tắc nghiệp vụ có thể kiểm chứng, ví dụ giới hạn số lượng, điều kiện hợp lệ, cách tính một số giá trị.
  • Workflow contract: mô tả chuỗi bước, sự kiện và điều kiện chuyển trạng thái của quy trình.
  • Policy contract: mô tả RBAC contract, quyền truy cập, điều kiện phê duyệt, phạm vi thao tác, audit và traceability liên quan đến chính sách.
  • API contract: mô tả bề mặt HTTP hoặc giao diện tích hợp, gồm endpoint, method, schema, mã lỗi, auth, versioning và các ràng buộc giao tiếp.

Khi các lớp này được tách rõ, HTTP contract chỉ cần mang phần thông tin thật sự tác động đến giao tiếp và triển khai API. Những gì thuộc domain, workflow hay policy thì nên được tham chiếu hoặc gắn định danh, không nên chép lại nguyên xi theo kiểu văn xuôi.

HTTP contract cần đủ đến mức nào

Một HTTP contract đủ để tạo code có thể dùng thường phải khóa được các nhóm thông tin sau.

  • Mục đích của từng endpoint: endpoint này phục vụ use case nào, actor nào gọi, điều kiện tiền đề là gì.
  • Method và route rõ ràng: ví dụ POST /orders hay GET /orders/{id}, tránh mô tả mơ hồ kiểu một API để xử lý đơn hàng.
  • Schema input có cấu trúc: tên trường, kiểu dữ liệu, bắt buộc hay tùy chọn, ràng buộc định dạng, giới hạn giá trị, quan hệ giữa các trường.
  • Schema output có cấu trúc: response thành công, response lỗi, danh sách field trả ra, field nào luôn có, field nào phụ thuộc trạng thái.
  • Error model: mã lỗi HTTP, mã lỗi nghiệp vụ, thông điệp máy đọc được, trường hợp nào sinh ra từng lỗi.
  • Auth và authorization: cơ chế xác thực, role nào hoặc policy nào được phép thao tác, khi nào bị từ chối.
  • Hành vi ảnh hưởng đến client: idempotency, pagination, sorting, filtering, rate limit, timeout, retry, optimistic locking nếu có.
  • Quan hệ với workflow: gọi endpoint này sẽ tạo trạng thái nào, phát sinh bước nào, có chờ phê duyệt hay xử lý nền hay không.
  • Versioning và traceability: version contract, mã use case, rule id hoặc policy id liên quan để truy vết từ contract sang code và test.

Nếu thiếu các điểm trên, code được tạo ra thường chỉ là khung giao tiếp, còn phần có thể dùng được vẫn phải đi hỏi lại rất nhiều. Ngược lại, bạn không cần nhồi vào HTTP contract các chi tiết như tên class, tên service nội bộ, cấu trúc thư mục, cách chia file hay mô tả từng câu lệnh triển khai.

Cách khóa phạm vi và ngôn ngữ để contract dùng được cho thi công

Muốn contract dùng được cho software factory, cần khóa cả phạm vi lẫn ngôn ngữ.

  • Khóa phạm vi: mỗi contract chỉ phục vụ một lát cắt rõ ràng. Ví dụ contract của tạo đơn hàng không nên gộp luôn duyệt đơn, thanh toán, hoàn tiền và đồng bộ kho nếu đó là các bước khác workflow.
  • Khóa thuật ngữ: một từ chỉ có một nghĩa. Nếu đã dùng từ order là đơn hàng đã được tạo, đừng chỗ khác lại dùng order để chỉ giỏ hàng đang nháp.
  • Khóa danh tính quy tắc: thay vì viết văn dài, nên gắn rule id hoặc policy id để đội triển khai và đội test cùng tham chiếu đúng một nguồn.
  • Khóa định dạng mô tả: phần máy cần đọc phải có cấu trúc, ví dụ schema, enum, state, permission matrix. Phần giải thích bằng văn xuôi chỉ dùng để bổ sung ngữ cảnh.
  • Khóa biên giới trách nhiệm: HTTP contract nói rõ cái gì client phải gửi, cái gì server tự suy ra, cái gì do policy quyết định, cái gì là side effect ngoài phạm vi response đồng bộ.

Contract-first chỉ có giá trị khi nhiều người cùng đọc ra cùng một ý nghĩa. Nếu ngôn ngữ trong contract quá mở, công cụ không sinh đúng code được và con người cũng không triển khai nhất quán được.

Phần nào nên viết ngắn, phần nào bắt buộc phải rõ

Một lỗi phổ biến là viết quá nhiều phần mô tả mở đầu nhưng lại viết quá ít ở những chỗ cần cấu trúc. Có thể áp dụng nguyên tắc sau.

  • Nên viết ngắn: bối cảnh tổng quan, giải thích tại sao có endpoint, ghi chú định hướng thiết kế, nhận xét kiến trúc nội bộ.
  • Bắt buộc phải rõ và có cấu trúc: request schema, response schema, enum, validation, error model, quyền truy cập, trạng thái, điều kiện chuyển bước, mã định danh rule hoặc workflow liên quan.

Nói ngắn gọn, những phần dùng để đọc hiểu có thể súc tích. Những phần dùng để thi công, sinh code, viết test và kiểm soát chất lượng thì phải chặt chẽ.

Lỗi thường gặp khi viết HTTP contract

  • Ôm hết mọi thứ vào một chỗ: domain, UI, database, workflow, policy và API bị trộn vào nhau khiến contract dài nhưng không sắc nét.
  • Bám sát code hiện tại quá mức: route, schema, tên field bị thiết kế để phản chiếu implementation thay vì phản ánh ngôn ngữ nghiệp vụ ổn định. Kết quả là chỉ cần refactor nội bộ cũng phải sửa contract vô ích.
  • Dùng văn xuôi cho các ràng buộc quan trọng: viết kiểu hệ thống sẽ kiểm tra hợp lệ nếu cần khiến người đọc không biết điều kiện hợp lệ là gì.
  • Không mô tả lỗi và ngoại lệ: chỉ có case thành công, còn những gì làm client vỡ thì phải tự đoán.
  • Không nối với workflow và policy: endpoint có vẻ đúng về mặt schema nhưng không nói nó tác động thế nào đến quy trình hay quyền hạn.
  • Không định danh để truy vết: không có rule id, use case id, version hay liên kết test, nên khi thay đổi rất khó biết thứ gì bị ảnh hưởng.

Một contract tốt và một contract tệ khác nhau ở đâu

Contract tệ thường có kiểu mô tả như sau: có API tạo đơn hàng, client gửi thông tin cần thiết, hệ thống sẽ kiểm tra hợp lệ, tính toán và trả kết quả phù hợp. Cách viết này nghe có vẻ đầy đủ nhưng gần như không đủ để tạo code dùng được. Không biết cần field nào, kiểm tra gì, lỗi nào, quyền nào, khi nào tạo thành công, khi nào chờ duyệt.

Contract tốt sẽ mô tả theo dạng có thể thi công: endpoint POST /orders; actor là sales hoặc customer đã xác thực; request gồm customer_id, items, payment_method; mỗi item có product_id và quantity; quantity phải lớn hơn 0; payment_method thuộc tập giá trị cho phép; nếu khách hàng bị khóa mua hàng thì trả lỗi quyền nghiệp vụ; nếu dữ liệu không hợp lệ thì trả lỗi validation; nếu thành công tạo order ở trạng thái pending hoặc approved tùy policy; response trả order_id, status, total; workflow liên kết với bước kiểm duyệt WF-ORDER-01; policy liên kết với RBAC-ORDER-CREATE. Đó là mức đủ để server stub, test case, validator và tài liệu client đều bám cùng một hợp đồng.

Điểm khác biệt cốt lõi là contract tốt làm giảm số câu hỏi phải hỏi lại trong lúc thi công. Contract tệ đẩy gánh nặng giải thích sang cuộc họp và tin nhắn.

Nguyên tắc thực hành cho contract-first với Midi Coder

  • Đừng xem HTTP contract là nơi chứa toàn bộ sự thật. Hãy để nó tham chiếu đúng sang domain contract, DSL contract, workflow contract và RBAC contract.
  • Thiết kế để máy đọc được ở những chỗ quan trọng: schema, enum, state, lỗi, permission, mapping rule.
  • Dùng traceability ngay từ đầu: mỗi endpoint nên nối được về use case, workflow và policy liên quan.
  • Tối ưu cho việc sinh phần lặp lại: validation, DTO, response model, test khung, permission guard, audit hook.
  • Chấp nhận rằng vẫn có phần phải lập trình thủ công: orchestration, chiến lược xử lý đặc thù, tối ưu hiệu năng, tích hợp ngoại lệ. Contract tốt không loại bỏ tư duy lập trình, nó làm rõ phạm vi tư duy cần thiết.

Kết luận

HTTP contract nên đủ để đội triển khai tạo ra code có thể dùng, chứ không chỉ tạo ra khung code nhìn đẹp. Muốn vậy, contract phải rõ ở những phần quyết định hành vi giao tiếp, validation, lỗi, quyền và liên hệ với workflow, nhưng phải tiết chế ở những phần thuộc chi tiết nội bộ. Một contract tốt không phải contract dài nhất. Đó là contract khiến đầu ra ít bất ngờ hơn, giảm suy đoán, tăng khả năng kiểm thử và giữ được sự nhất quán khi hệ thống phát triển.

Frequently Asked Questions

HTTP contract có cần mô tả toàn bộ logic nghiệp vụ không?

Không. HTTP contract nên mô tả phần logic ảnh hưởng trực tiếp đến giao tiếp API như validation, lỗi, quyền và trạng thái. Logic nghiệp vụ sâu nên nằm ở domain, rule hoặc workflow contract và được tham chiếu lại.

Nếu đã có OpenAPI thì có đủ để tạo code dùng được chưa?

Chưa chắc. OpenAPI giúp mô tả route và schema tốt, nhưng để code dùng được còn cần nối với policy, workflow, error model nghiệp vụ và traceability. Nếu những phần đó mơ hồ, code sinh ra vẫn chỉ là khung.

Contract-first có làm hệ thống cứng nhắc hơn không?

Chỉ khi contract được dùng sai cách. Contract-first tốt sẽ khóa những phần cần ổn định và để mở những phần thuộc implementation nội bộ. Mục tiêu là giảm mơ hồ, không phải đóng băng sáng tạo kỹ thuật.

Dấu hiệu nào cho thấy HTTP contract đang quá chi tiết?

Khi tài liệu chứa tên class, cấu trúc thư mục, chi tiết module nội bộ hoặc mô tả những quyết định triển khai không ảnh hưởng đến client. Đó là lúc contract đang bám code quá mức thay vì mô tả hợp đồng giao tiếp.