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

Một bộ contract tối thiểu của Midi Coder nên gồm những phần nào

Một bộ contract tối thiểu của Midi Coder nên đủ chặt để dùng cho thi công, nhưng đủ gọn để không biến thành giấy tờ. Cốt lõi thường gồm 6 lớp: domain, app, rule, workflow, policy và API, được viết bằng ngôn ngữ hẹp, có cấu trúc và truy vết rõ ràng.

Huỳnh Kim Đạt Huỳnh Kim Đạt
2 lượt xem 7 phút đọc
Một bộ contract tối thiểu của Midi Coder nên gồm những phần nào

TL;DR

Midi Coder nên có 6 lớp contract tối thiểu: domain, app, rule, workflow, policy và API. Mỗi lớp cần phạm vi rõ, ngôn ngữ hẹp và cấu trúc đủ chặt để có thể dùng trực tiếp cho code, test và truy vết.

Key Takeaways

  • Domain contract định nghĩa entity, value object, state và glossary.
  • App contract mô tả capability, input, output, precondition và postcondition.
  • Rule contract gom các luật nghiệp vụ có thể kiểm tra và truy vết.
  • Workflow contract phải chỉ rõ transition, trigger, guard condition và audit.
  • Policy contract là nơi chuẩn hóa RBAC contract và scope truy cập.
  • API contract nên bám vào domain và rule, không tự phát sinh ngôn ngữ riêng.

Trong cách làm contract-first, giá trị của contract không nằm ở việc viết thật nhiều tài liệu, mà ở việc giảm bất ngờ khi bước vào giai đoạn contract coding. Với Midicoder, một bộ contract tối thiểu nên đủ để đội phát triển, kiểm thử và vận hành cùng hiểu một hệ thống theo cùng một ngôn ngữ, nhưng không ôm mọi chi tiết kỹ thuật đến mức khó dùng.

Một bộ contract tốt thường trả lời được 3 câu hỏi: hệ thống đang quản lý cái gì, hệ thống được phép làm gì và hệ thống phải đi qua các bước nào. Từ đó, Midicoder có thể tổ chức contract thành 6 lớp cốt lõi: domain, app, rule, workflow, policyAPI.

1. Domain contract: định nghĩa sự vật và ngôn ngữ chung

Domain contract là lớp nền. Nó mô tả các thực thể, giá trị, trạng thái, định danh và quan hệ mà hệ thống sử dụng. Nếu lớp này mơ hồ, mọi lớp phía trên sẽ mơ hồ theo.

Một domain contract tối thiểu nên có:

  • Entity: đối tượng chính như User, Order, Invoice, ApprovalRequest.
  • Value object: các giá trị có quy tắc riêng như Email, Money, DateRange.
  • State: các trạng thái hợp lệ của entity.
  • Invariant: điều luôn phải đúng, ví dụ tổng tiền không âm, người duyệt không được là người tạo.
  • Glossary: từ điển thuật ngữ để tránh mỗi nhóm hiểu một kiểu.

Điều quan trọng là domain contract không nên viết như code, cũng không nên viết như tài liệu marketing. Nó phải ngắn, chuẩn hóa từ vựng và chỉ giữ những khái niệm có tác động đến hành vi hệ thống.

2. App contract: xác định các capability ở mức ứng dụng

App contract mô tả hệ thống làm được những việc gì ở góc nhìn nghiệp vụ: tạo yêu cầu, phê duyệt, hủy, tra cứu, xuất báo cáo, đồng bộ dữ liệu. Đây là lớp chuyển từ thế giới khái niệm sang thế giới hành động.

App contract nên có cấu trúc tối thiểu:

  • Use case hoặc capability name.
  • Input và điều kiện đầu vào.
  • Output hoặc kết quả mong đợi.
  • Preconditionpostcondition.
  • Side effects nếu có: gửi mail, sinh log, tạo event.

Nếu domain contract trả lời “cái gì tồn tại”, app contract trả lời “người dùng hay hệ thống được làm gì với nó”.

3. Rule contract: gom các luật nghiệp vụ phải thi hành nhất quán

Rule contract là nơi dễ bị viết thiếu hoặc viết trôi nổi trong code nhất. Với Midi Coder, lớp này rất quan trọng vì nó giúp chuyển luật nghiệp vụ thành cấu trúc có thể kiểm tra, truy vết và thực thi được.

Một rule contract tối thiểu nên mô tả:

  • Rule id để truy vết.
  • Ngữ cảnh áp dụng: áp dụng cho entity nào, hành động nào.
  • Điều kiện: when.
  • Ràng buộc hoặc kết quả: then.
  • Mức độ: chặn, cảnh báo, gợi ý.
  • Nguồn gốc: do pháp lý, nội bộ hay nghiệp vụ yêu cầu.

Ví dụ: “Nếu giá trị đơn hàng vượt 100 triệu thì bắt buộc đi qua cấp phê duyệt tài chính.” Đây là một rule nên nằm trong contract, không nên chỉ nằm trong đầu một lập trình viên hoặc trong một nhánh if khó tìm.

4. Workflow contract: mô tả luồng và điểm chuyển trạng thái

Workflow contract định nghĩa cách một đối tượng hoặc một quy trình đi từ trạng thái này sang trạng thái khác. Đây là phần rất gần với thực tế vận hành nên phải rõ ràng, nhưng không cần biến thành sơ đồ rối.

Một workflow contract tối thiểu nên có:

  • Start stateend state.
  • Transition: từ trạng thái nào sang trạng thái nào.
  • Trigger: ai hoặc sự kiện nào kích hoạt.
  • Guard condition: điều kiện được phép chuyển.
  • Failure path hoặc ngoại lệ chính.
  • Audit requirement: cần lưu vết gì khi chuyển trạng thái.

Điểm cần tránh là mô tả workflow bằng lời kể dài dòng. Workflow muốn dùng cho thi công thì phải có cấu trúc, có tên bước rõ ràng và có thể map sang kiểm thử.

5. Policy contract: khóa quyền, phạm vi và kiểm soát truy cập

Policy contract là lớp nhiều đội bỏ sót rồi sau đó vá bằng điều kiện phân tán trong controller hoặc UI. Với hệ thống có nhiều vai trò, đây là nguồn gốc của lỗi quyền truy cập nếu không được chuẩn hóa.

Một policy contract tối thiểu nên gồm:

  • Role hoặc actor.
  • Action được phép làm.
  • Resource bị tác động.
  • Scope: trong phòng ban của mình, trong tenant của mình, hay toàn hệ thống.
  • Condition: điều kiện bổ sung.
  • Decision: allow, deny, require approval.

Đây cũng là lớp phù hợp để thiết kế theo hướng RBAC contract, hoặc kết hợp thêm điều kiện ngữ cảnh khi cần. Viết rõ policy giúp code bớt if-else ngầm và giúp kiểm thử quyền trở nên có hệ thống hơn.

6. API contract: đóng gói giao tiếp với hệ thống

API contract không phải là toàn bộ contract, nhưng là lớp giao tiếp quan trọng khi hệ thống có frontend, integration hoặc public endpoint. Một API contract tốt phải bám vào domain, app và rule phía trên, thay vì tự phát sinh ngôn ngữ riêng.

Một API contract tối thiểu nên có:

  • Endpoint và method.
  • Input schema.
  • Output schema.
  • Error model.
  • Authenticationauthorization.
  • Idempotency hoặc ràng buộc gọi lặp nếu cần.

API contract nên rõ về schema và lỗi, nhưng không cần chép lại toàn bộ business rule nếu rule đó đã có trong lớp rule contract. Mục tiêu là nhất quán, không trùng lặp vô ích.

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

Lý do nhiều bộ contract thất bại là vì phạm vi quá rộng và ngôn ngữ quá mở. Midicoder nên ưu tiên một DSL contract hoặc cấu trúc viết đủ hẹp để mọi người hiểu giống nhau.

Có thể áp dụng các nguyên tắc sau:

  • Mỗi contract chỉ phục vụ một mục đích: domain để định nghĩa, rule để ràng buộc, workflow để luồng, policy để quyền.
  • Mỗi câu phải có khả năng kiểm thử: nếu không thể viết test hoặc checklist từ một dòng contract, dòng đó có thể đang quá mơ hồ.
  • Giới hạn từ vựng: dùng glossary thống nhất, tránh từ đồng nghĩa không kiểm soát.
  • Ưu tiên cấu trúc hơn văn xuôi: id, condition, action, result, state, actor.
  • Truy vết được: rule nào sinh ra API nào, workflow nào dùng policy nào.

Nói cách khác, contract không nên là nơi để “giải thích mọi thứ”, mà là nơi để “quy định đủ thứ cần thi công”.

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

Nên viết ngắn ở các phần:

  • Giới thiệu bối cảnh.
  • Mục tiêu nghiệp vụ tổng quan.
  • Ví dụ minh họa không bắt buộc.

Bắt buộc phải rõ và có cấu trúc ở các phần:

  • Schema dữ liệu cốt lõi.
  • Luật nghiệp vụ.
  • Chuyển trạng thái trong workflow.
  • Quyền truy cập và phạm vi.
  • Input/output/error của API.

Nếu cần chọn chỗ để đầu tư kỹ, hãy đầu tư vào các phần có tác động trực tiếp đến code, test, log và vận hành.

Lỗi thường gặp khi thiết kế contract

  • Ôm hết mọi thứ: contract biến thành tài liệu tổng hợp, đọc rất dài nhưng không ai thi công theo được.
  • Bám sát code quá mức: contract chỉ là bản chép lại class, function, table nên mất vai trò làm chuẩn nghiệp vụ.
  • Trùng lặp khái niệm: cùng một rule xuất hiện ở nhiều nơi với câu chữ khác nhau.
  • Không có traceability: không biết requirement nào dẫn tới rule nào, rule nào ảnh hưởng API nào.
  • Thiếu policy: quyền bị nhúng rải rác vào giao diện và backend.
  • Workflow mơ hồ: trạng thái có tên nhưng không có điều kiện chuyển rõ ràng.

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

Contract tệ:

“Người dùng có thể tạo yêu cầu và nếu cần thì sẽ được duyệt theo quy trình phù hợp. Một số người có quyền xem hoặc sửa tùy theo vai trò.”

Vấn đề là câu này nghe có vẻ đúng, nhưng gần như không thể dùng để code hay kiểm thử.

Contract tốt:

  • Entity: ApprovalRequest.
  • State: draft, submitted, approved, rejected, cancelled.
  • Rule R-01: nếu amount >= 100000000 thì required_approver_role = finance_manager.
  • Workflow W-02: submitted -> approved, trigger = approver action, guard = actor has permission approve_request and actor is not creator.
  • Policy P-03: role = department_manager, action = read, resource = ApprovalRequest, scope = department_id matches actor.department_id.
  • API POST /approval-requests: input schema gồm requester_id, amount, reason; error = INVALID_AMOUNT, MISSING_REASON, POLICY_DENIED.

Contract tốt không nhất thiết dài hơn nhiều, nhưng chặt hơn nhiều. Nó giúp giảm tranh cãi, giảm diễn giải cá nhân và tăng khả năng tự động hóa.

Kết luận

Một bộ contract tối thiểu của Midi Coder nên gồm 6 lớp: domain contract, app contract, rule contract, workflow contract, policy contractAPI contract. Không phải dự án nào cũng cần mọi thứ ở cùng độ sâu, nhưng nếu thiếu một trong các lớp cốt lõi này thì khả năng cao logic sẽ trôi vào code theo cách khó kiểm soát.

Điểm mấu chốt là giữ contract ở đúng vai trò: đủ ngắn để đọc được, đủ cấu trúc để thi công được và đủ rõ để truy vết được. Một contract tốt là contract khiến code đầu ra ít bất ngờ hơn.

Frequently Asked Questions

Midi Coder có cần viết contract rất dài để đủ chi tiết không?

Không. Contract nên đủ dùng cho thi công, không phải càng dài càng tốt. Phần cần ngắn là bối cảnh và mô tả tổng quan; phần cần chặt là schema, rule, workflow, policy và API.

Vì sao policy contract quan trọng trong Midi Coder?

Vì nếu không có policy contract, quyền truy cập thường bị rải rác trong UI và backend, gây khó kiểm thử, khó truy vết và dễ sinh lỗi bảo mật.

Rule contract khác gì với API contract?

Rule contract mô tả luật nghiệp vụ cốt lõi của hệ thống, còn API contract mô tả cách giao tiếp với hệ thống qua endpoint, schema và error. API nên phản ánh rule, không thay thế rule.

Một DSL contract tốt cần đặc điểm gì?

Nó cần ngôn ngữ hẹp, từ vựng thống nhất, cấu trúc rõ và mỗi dòng nên có khả năng chuyển thành code, test hoặc kiểm tra tự động.