How we rolled out our own permissions DSL at Figma
Bài tóm tắt Figma 2024 4. Điều chỉnh Độ khó: Khó
Figma · 2024 · 4. Điều chỉnh · Độ khó: Khó — bài gốc
Bối cảnh
- Quyền ở Figma phức tạp vì sản phẩm cộng tác: truy cập file qua role phân cấp (folder, team, org) hoặc qua link (ai, mức nào, bao lâu, mật khẩu, chính sách của org).
- Ban đầu toàn bộ logic nằm trong monolith Ruby: mỗi model ActiveRecord có hàm
has_access?, một chuỗi if/else gọi DB rồi trả boolean. Đầu 2021 xuất hiện hàng loạt bug, ticket hỗ trợ và dự án trễ vì nó. - Bốn vấn đề:
- hàm dài, nhiều tham số tuỳ chọn; sửa một rule là phải gánh toàn bộ logic quyền của resource đó; bug có thể làm lộ mọi file;
- mức quyền là số nguyên phân cấp (100 xem, 300 sửa) nhưng bị vá bằng cờ boolean (
ignore_link_access…) khác nhau theo resource; - khoảng 20% tải DB là từ kiểm tra quyền, đúng lúc DB đang chạm giới hạn; truy vấn và logic dính chặt nên không tối ưu được truy vấn mà không sợ đổi logic;
- hai nguồn sự thật: logic phải viết hai lần, ở Sinatra (HTTP) và LiveGraph (API realtime), và hay lệch nhau.
Thay đổi chính
- Build thay vì buy: đã xét Open Policy Agent, Zanzibar, Oso nhưng theo Figma không cái nào giải đúng vấn đề; tự làm DSL, engine đa nền tảng và migrate các rule quan trọng nhất.
- Policy kiểu IAM: mỗi policy có loại resource, effect (allow/deny, deny thắng allow), danh sách permission, điều kiện. Policy độc lập nhau nên người viết không phải biết các policy khác.
- Bước giảm rủi ro lớn nhất: viết lại mọi logic cũ thành policy (bản Ruby đầu tiên) trên một nhánh cho tới khi hàng trăm test tích luỹ nhiều năm đều xanh; mất vài tuần, qua đó hiểu hết ngóc ngách và lý do sản phẩm.
- DSL dạng JSON: mở rộng
ExpressionDefcó sẵn của đội LiveGraph: bộ ba[trường, toán tử, giá trị hoặc ref]ghép bằngand/or/not, trường viết dạng"file.deleted_at". Policy viết bằng TypeScript có kiểu rồi biên dịch ra JSON; không chạy code, nên không có network call hay side-effect trong policy. - Engine nhỏ, nhiều bản:
ApplyEvaluator(đánh giá boolean) viết cho TypeScript, Ruby, sau thêm Go; mỗi bản mất 2–3 ngày công của một senior, dùng chung một bộ test. - Tách logic khỏi nạp dữ liệu: từ policy suy ra tĩnh các bảng/cột cần;
DatabaseLoadersuy ra dòng nào từcontext_pathcủa resource và user. Engine toàn quyền chọn thứ tự, replica hay primary, cache. - Tối ưu: thêm trạng thái "chưa xác định" (
null) cho evaluator; nạp dữ liệu theo từng đợt, ưu tiên bảng hay quyết định kết quả (sau link access là file, folder, team role), có kết quả chắc chắn thì dừng. Thời gian đánh giá giảm hơn một nửa, giảm tải DB. - Công cụ: debugger trên frontend (nhập user ID và resource ID, mở từng nhánh
and/orxem giá trị) cho cả engineer và support; debug từng policy ở CLI khi chạy test; linter tĩnh trong CI bắt lỗi kiểu so sánh hai trường cùngnullthànhtrue.
Vì sao
- Theo Figma: tách policy khỏi data loading để người viết luật không lo hiệu năng, người tối ưu engine không lo logic; JSON cho phép một định nghĩa chạy ở mọi ngôn ngữ mà không cần parse AST. Họ cố giữ engine thật đơn giản, vì chỉ như thế mới duy trì được nhiều bản engine.
- Kết quả theo Figma: gần như hết sự cố và bug do logic lệch giữa Ruby và LiveGraph; support tự gỡ được thắc mắc về quyền.
Bài học cho SWE
- Hàm
has_access?phình to là dấu hiệu cần chuyển sang policy rời, có effect deny/allow rõ ràng. - Trước khi đổi engine phân quyền, dịch toàn bộ logic cũ sang mô hình mới và chạy qua bộ test cũ: đó là cách giảm rủi ro rẻ nhất.
- Policy là dữ liệu (không phải code) thì phân tích tĩnh, debug trực quan, chạy đa nền tảng đều rẻ.
Liên quan
- Figma 2023 · Figma 2024 (áp lực DB cùng thời) · Airbnb 2021 (hướng Zanzibar mà Figma không chọn) · Uber 2023 (cũng là policy + điều kiện)
- Xác thực và phân quyền (authn/authz) · Monolith và microservices — đi và quay lại
#Authorization#Permissions DSL#Policy#Allow/Deny#IAM-style policy#ExpressionDef#JSON-serializable#Cross-platform engine#Data loader#Short circuit#Static analysis#LiveGraph#Build vs buy
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (5)