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

Contract tốt không cần dài, contract tốt cần khóa được quyết định nào

Một contract tốt không được đo bằng số trang mà bằng khả năng khóa đúng quyết định để đội ngũ thi công ít hiểu sai hơn. Bài viết phân tách các lớp contract cốt lõi, chỉ ra phần nào cần ngắn gọn, phần nào phải có cấu trúc rõ ràng, và cách tránh biến contract thành giấy tờ vô dụng hoặc bản sao của code.

Huỳnh Kim Đạt Huỳnh Kim Đạt
16 lượt xem 8 phút đọc
Contract tốt không cần dài, contract tốt cần khóa được quyết định nào

TL;DR

Độ dài không quyết định chất lượng contract. Một contract tốt phải khóa đúng các quyết định cốt lõi như phạm vi, ngôn ngữ, quy tắc, workflow, phân quyền và API để đầu ra của code ít bất ngờ hơn. Hãy tách các lớp contract rõ ràng và cấu trúc hóa phần có thể kiểm chứng.

Key Takeaways

  • Contract tốt không cần dài, nhưng phải khóa được các quyết định cốt lõi.
  • Nên tách contract thành các lớp: domain, app, rule, workflow, policy và API.
  • Phần định nghĩa, quy tắc, quyền và schema nên được cấu trúc hóa thay vì chỉ viết văn xuôi.
  • Contract ôm hết mọi thứ sẽ khó bảo trì; contract bám sát code quá mức sẽ mất vai trò kiến trúc.
  • Mục tiêu cuối cùng là làm cho quá trình thi công và code đầu ra ít bất ngờ hơn.

Trong nhiều đội phát triển, contract thường rơi vào một trong hai thái cực: hoặc quá dài đến mức không ai đọc hết, hoặc quá mỏng đến mức mỗi người hiểu một kiểu. Vấn đề không nằm ở độ dài. Vấn đề nằm ở việc contract có khóa được những quyết định quan trọng hay không.

Một contract tốt là contract làm cho đầu ra của quá trình thi công ít bất ngờ hơn. Nó không cần ôm toàn bộ thế giới. Nó cần xác định rõ phạm vi, ngôn ngữ, ràng buộc và điểm giao tiếp giữa những người cùng tham gia xây dựng sản phẩm. Đó cũng là tinh thần của contract-first: quyết định cái gì phải ổn định trước, cái gì có thể thay đổi sau, và cái gì cần truy vết xuyên suốt từ business đến code.

Trong cách làm của Midi Coder và các mô hình software factory, contract không chỉ là tài liệu mô tả. Contract là vật mang quyết định, là đầu vào cho thi công, là cơ sở để traceability, và trong nhiều trường hợp còn là nền cho DSL contract, workflow contract, RBAC contract hay API contract.

Contract tốt là contract khóa đúng quyết định

Nếu mọi thứ đều được đưa vào contract, contract sẽ biến thành bãi chứa thông tin. Nếu không khóa gì cả, contract chỉ còn là lời kể. Do đó câu hỏi quan trọng nhất khi thiết kế contract không phải là viết bao nhiêu, mà là cần khóa quyết định nào để giảm mơ hồ.

Thông thường, một contract tốt nên khóa các nhóm quyết định sau:

  • Phạm vi: bài toán này bao gồm gì, không bao gồm gì.
  • Ngôn ngữ: thuật ngữ nào là chuẩn, khái niệm nào bị cấm dùng lẫn nhau.
  • Ràng buộc nghiệp vụ: điều kiện đúng sai, ngoại lệ, giới hạn, trạng thái.
  • Luồng xử lý: bước nào bắt buộc, ai được kích hoạt, khi nào chuyển trạng thái.
  • Phân quyền: ai được xem, tạo, sửa, duyệt, hủy.
  • Điểm giao tiếp kỹ thuật: input, output, schema, version, lỗi, tính tương thích.

Nếu một contract khóa được các quyết định này, đội phát triển sẽ ít tranh cãi lại những điều đã chốt, QA có chỗ bám để kiểm thử, và hệ thống dễ được triển khai theo hướng nhất quán hơn.

Các lớp contract cốt lõi

Một sai lầm phổ biến là dùng một loại contract để giải quyết mọi vấn đề. Trên thực tế, contract nên được tách thành các lớp, mỗi lớp giữ một vai trò rõ ràng.

1. Domain contract

Domain contract định nghĩa ngôn ngữ của bài toán: thực thể, thuộc tính, trạng thái, quan hệ, thuật ngữ. Đây là nơi khóa nghĩa của từ. Ví dụ, nếu hệ thống có các khái niệm như đơn nháp, đơn chờ duyệt, đơn đã phát hành, thì domain contract phải làm rõ mỗi trạng thái có nghĩa gì và khác nhau ở đâu.

Một domain contract tốt không cần kể chuyện dài dòng. Nó cần chính xác về từ vựng. Khi từ vựng sai, mọi lớp phía sau đều sai theo.

2. App contract

App contract mô tả các năng lực mà ứng dụng cung cấp cho người dùng hoặc hệ thống khác. Đây là lớp trả lời câu hỏi: người dùng có thể làm gì với hệ thống, theo các trường hợp sử dụng nào, với đầu vào và kết quả mong đợi ra sao.

App contract không nên sa vào chi tiết implementation. Nó nên đủ cụ thể để đội thi công biết mình đang xây cái gì.

3. Rule contract

Rule contract giữ các quy tắc nghiệp vụ có thể kiểm chứng. Đây là phần đặc biệt quan trọng vì nó trực tiếp ảnh hưởng đến hành vi của hệ thống. Ví dụ: một yêu cầu chỉ được duyệt khi đã có đủ hồ sơ, hoặc giá trị vượt ngưỡng phải qua hai lớp phê duyệt.

Quy tắc nên được viết theo cấu trúc rõ ràng, có điều kiện, có kết quả, có ngoại lệ nếu cần. Rule contract là nơi rất phù hợp để biểu diễn bằng DSL contract nếu muốn tăng tính nhất quán và khả năng tự động hóa.

4. Workflow contract

Workflow contract khóa luồng chuyển trạng thái và trách nhiệm xử lý. Nó mô tả bước nào diễn ra trước, bước nào diễn ra sau, điều kiện nào cho phép chuyển bước, và tín hiệu nào đánh dấu hoàn tất.

Khi không có workflow contract rõ ràng, hệ thống thường phát sinh xử lý vòng vo, nhảy bước hoặc chồng chéo trách nhiệm giữa các vai trò.

5. Policy contract

Policy contract giữ các chính sách vận hành như RBAC contract, hạn mức, điều kiện kiểm soát, quy định tuân thủ. Lớp này tách phần chính sách ra khỏi chi tiết use case giúp hệ thống dễ mở rộng và kiểm soát hơn.

RBAC contract đặc biệt quan trọng vì nó giúp khóa quyền theo vai trò thay vì để logic quyền rải rác trong code. Khi policy rõ, việc kiểm thử quyền và truy vết thay đổi cũng rõ hơn.

6. API contract

API contract là điểm giao tiếp kỹ thuật giữa các thành phần. Nó cần rõ schema, kiểu dữ liệu, ràng buộc, mã lỗi, tương thích ngược, version và ví dụ trao đổi dữ liệu. Đây là lớp contract gần code nhất, nhưng không nên bị đồng nhất với code.

API contract tốt giúp frontend, backend, QA và tích hợp làm việc song song. Tuy vậy, một API contract tốt chỉ thật sự phát huy khi phía trên nó đã có domain, rule và workflow đủ rõ.

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

Nhiều contract thất bại không phải vì thiếu thông tin mà vì không khóa phạm vi và ngôn ngữ. Khi đó người đọc tưởng như đã hiểu nhưng thực ra mỗi người đang dùng một mô hình khác nhau trong đầu.

Để contract dùng được cho thi công, cần làm ít nhất bốn việc:

  • Khóa biên giới bài toán: nêu rõ trong phạm vi và ngoài phạm vi.
  • Khóa từ vựng: một thuật ngữ chỉ có một nghĩa trong ngữ cảnh của contract.
  • Khóa cấu trúc: phần nào là định nghĩa, phần nào là quy tắc, phần nào là ví dụ.
  • Khóa khả năng thay đổi: mục nào ổn định, mục nào có thể điều chỉnh theo version.

Đây là lý do các đội theo contract coding thường ưu tiên cấu trúc hóa contract thành schema, bảng quyết định, ma trận quyền, state machine, hoặc DSL thay vì chỉ viết văn xuôi. Văn xuôi hữu ích cho bối cảnh. Nhưng quyết định quan trọng nên được đặt trong cấu trúc để giảm diễn giải tự do.

Phần nào nên viết ngắn, phần nào bắt buộc phải rõ và có cấu trúc

Không phải phần nào cũng cần dài. Thực tế, nhiều phần càng ngắn càng tốt.

Nên viết ngắn

  • Mục tiêu của bài toán.
  • Phạm vi áp dụng.
  • Nguyên tắc thiết kế chính.
  • Giả định và ràng buộc cấp cao.

Các phần này nên ngắn vì mục đích là định hướng, không phải tranh luận học thuật.

Bắt buộc phải rõ và có cấu trúc

  • Định nghĩa thực thể và trường dữ liệu quan trọng.
  • Quy tắc nghiệp vụ có điều kiện.
  • Luồng trạng thái và điều kiện chuyển tiếp.
  • Ma trận quyền và trách nhiệm.
  • Schema trao đổi dữ liệu và lỗi.

Nếu các phần này vẫn viết mơ hồ, contract sẽ không đủ sức khóa quyết định. Đội thi công buộc phải tự lấp khoảng trống bằng suy đoán. Kết quả là sản phẩm ra đời nhanh nhưng thiếu nhất quán và khó truy vết.

Lỗi thường gặp khi contract ôm hết mọi thứ hoặc bám sát code quá mức

Ôm hết mọi thứ

Khi contract cố bao phủ toàn bộ chi tiết sản phẩm, nó nhanh chóng trở thành tài liệu khó bảo trì. Người viết không còn phân biệt đâu là quyết định cốt lõi, đâu là thông tin tham khảo. Mỗi lần thay đổi nhỏ đều kéo theo cập nhật dây chuyền, khiến tài liệu nhanh lỗi thời.

Biểu hiện dễ thấy là contract có rất nhiều mô tả nhưng rất ít phần có thể kiểm chứng trực tiếp.

Bám sát code quá mức

Ở chiều ngược lại, có những contract thực chất chỉ là bản sao của implementation hiện tại: tên class, tên hàm, cấu trúc bảng, chi tiết framework. Cách viết này làm contract mất vai trò kiến trúc. Khi code thay đổi, contract đổi theo, nhưng những quyết định ở tầng domain hay policy vẫn không được phát biểu rõ.

Một contract tốt nên gần enough để thi công, nhưng xa enough để không bị trói vào chi tiết triển khai tạm thời.

Ví dụ: contract tốt và contract tệ khác nhau ở đâu

Ví dụ contract tệ

Yêu cầu duyệt đơn: Hệ thống cho phép người dùng gửi đơn và quản lý có thể duyệt. Nếu hợp lệ thì duyệt, nếu không thì từ chối. Hệ thống cần lưu lịch sử và phân quyền phù hợp.

Đoạn mô tả này nghe có vẻ ổn nhưng gần như không khóa được quyết định nào. “Hợp lệ” là gì? Ai là “quản lý”? Có bao nhiêu trạng thái? Có được duyệt lại không? Lưu lịch sử đến mức nào? Phân quyền theo người hay theo vai trò?

Ví dụ contract tốt

Domain: Đơn có các trạng thái draft, submitted, approved, rejected, cancelled.

Rule: Đơn chỉ được chuyển từ draft sang submitted khi đủ 3 trường bắt buộc: người yêu cầu, lý do, chi phí ước tính. Đơn có chi phí lớn hơn 50.000.000 phải qua hai cấp duyệt.

Workflow: Người tạo đơn gửi đơn. Trưởng bộ phận duyệt cấp 1. Nếu vượt ngưỡng, giám đốc duyệt cấp 2. Bất kỳ cấp nào từ chối thì trạng thái chuyển sang rejected và bắt buộc có lý do.

RBAC contract: Vai trò requester được tạo, sửa đơn khi ở draft; vai trò manager được duyệt cấp 1; vai trò director được duyệt cấp 2; vai trò auditor chỉ được xem lịch sử.

API contract: API nộp đơn nhận payload theo schema xác định, trả về mã lỗi VALIDATION_ERROR nếu thiếu trường bắt buộc, FORBIDDEN nếu vai trò không hợp lệ.

Ví dụ thứ hai ngắn hơn nhiều tài liệu dày hàng chục trang, nhưng nó khóa được các quyết định cốt lõi. Đó là khác biệt giữa contract dùng để thi công và tài liệu chỉ để tham khảo.

Từ tài liệu sang năng lực thi công

Khi contract được tổ chức tốt, đội ngũ có thể dùng nó như nền cho contract-first delivery: phân tích, thiết kế, sinh test case, tạo checklist review, kiểm tra traceability, và thậm chí sinh một phần code hoặc cấu hình nếu DSL đủ chặt.

Đó cũng là điểm mạnh của mô hình software factory: tri thức không chỉ nằm trong đầu từng cá nhân mà được đóng gói thành contract có cấu trúc, có khả năng tái sử dụng, kiểm tra và mở rộng.

Với Midi Coder, cách tiếp cận này đặc biệt phù hợp cho các hệ thống cần phối hợp nhiều lớp quyết định: domain contract để khóa ngôn ngữ, workflow contract để khóa vận hành, RBAC contract để khóa quyền, API contract để khóa giao tiếp kỹ thuật. Khi các lớp này liên kết được với nhau, traceability trở nên thực tế hơn thay vì chỉ là khẩu hiệu.

Kết luận

Contract tốt không cần dài. Contract tốt cần khóa được những quyết định mà nếu không khóa, đội thi công sẽ hiểu khác nhau và làm ra kết quả khó đoán. Viết ít nhưng đúng chỗ sẽ giá trị hơn viết nhiều nhưng không kiểm chứng được.

Nếu phải nhớ một tiêu chí duy nhất, hãy nhớ điều này: contract tốt là contract khiến code đầu ra ít bất ngờ hơn. Khi contract làm được điều đó, nó không còn là giấy tờ. Nó trở thành một phần thực sự của năng lực xây dựng sản phẩm.

Frequently Asked Questions

Vì sao contract không nên quá dài?

Vì độ dài không tự tạo ra sự rõ ràng. Contract quá dài thường trộn lẫn quyết định cốt lõi với thông tin tham khảo, khiến người đọc khó tìm phần cần bám để thi công.

Một contract tốt cần khóa những gì?

Tối thiểu nên khóa phạm vi, ngôn ngữ, quy tắc nghiệp vụ, workflow, phân quyền và điểm giao tiếp kỹ thuật như schema và mã lỗi.

Khi nào nên dùng DSL contract?

Nên dùng khi quy tắc, workflow hoặc policy có thể biểu diễn bằng cấu trúc rõ ràng để tăng tính nhất quán, khả năng kiểm tra và khả năng tự động hóa.

API contract có đủ để làm contract-first không?

Không. API contract chỉ giải quyết lớp giao tiếp kỹ thuật. Nếu domain, rule, workflow và policy chưa rõ thì API vẫn có thể đúng về kỹ thuật nhưng sai về hành vi nghiệp vụ.