Ngăn Chặn Property Drilling trong FastAPI với Request-Level Globals
Giới Thiệu
Trong quá trình phát triển ứng dụng với FastAPI, việc truyền dữ liệu qua các lớp khác nhau có thể trở thành một cơn ác mộng, đặc biệt là khi bạn phải truyền cùng một thông tin như ngữ cảnh người dùng hoặc phiên database qua nhiều hàm và lớp. Bài viết này sẽ hướng dẫn bạn cách tối ưu hóa mã của mình bằng cách sử dụng biến ngữ cảnh ở cấp độ yêu cầu, giúp giảm thiểu việc truyền thông tin không cần thiết qua các lớp, đồng thời cải thiện khả năng kiểm tra và bảo trì mã.
Tình Huống Hiện Tại: Cơn Ác Mộng Property Drilling 🙄
Trước khi áp dụng giải pháp mới, mã của tôi trông như sau:
python
# controller.py
@router.post("/projects")
async def create_project(
data: ProjectRequest,
db: Session = Depends(get_db),
context: Context = Depends(get_context)
):
return await project_service.create_project(data, context)
Mã này đã phải truyền context qua nhiều lớp chỉ để có thể kiểm tra quyền hạn trong chính sách. Điều này không chỉ làm mã trở nên khó đọc mà còn dễ dẫn đến lỗi.
Cảm Hứng Từ Nguồn Khác
Tôi đã tình cờ xem một video của DHH trên YouTube về việc viết phần mềm tốt. Ông đã đề cập đến khái niệm Current trong Ruby, và tôi đã quyết định áp dụng một cách tương tự trong FastAPI của mình. Dù không biết rõ cách hoạt động của nó trong Ruby, nhưng tôi đã cố gắng tái hiện ý tưởng này để làm giảm mã và cải thiện khả năng kiểm tra.
Giải Pháp: Sử Dụng Biến Ngữ Cảnh Ở Cấp Độ Yêu Cầu
Tạo Biến Ngữ Cảnh
Đầu tiên, chúng ta sẽ tạo một biến ngữ cảnh trong Python ngay từ đầu yêu cầu:
python
# context.py
from contextvars import ContextVar
from pydantic import BaseModel
class Context(BaseModel):
issuer_id: str
issuer_role: str
# Các thuộc tính khác...
Middleware Để Đặt Ngữ Cảnh
Chúng ta cần một middleware để thiết lập biến ngữ cảnh khi có yêu cầu đến:
python
# middleware.py
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
class CurrentContextMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
user = await get_current_user_from_request(request)
context = Context.for_user(user)
Current.set(context)
try:
response = await call_next(request)
return response
finally:
Current.clear()
Mã Sau Khi Tối Ưu Hóa
Sau khi áp dụng biến ngữ cảnh, mã sẽ trông như sau:
python
# controller.py
@router.post("/projects")
async def create_project(data: ProjectRequest):
return await project_service.create_project(data)
Hướng Dẫn Thực Hành
Các Bước Thực Hiện
- Tạo lớp Context: Định nghĩa lớp
Contextđể chứa thông tin cần thiết cho mỗi yêu cầu. - Tạo middleware: Tạo middleware để thiết lập và xóa ngữ cảnh sau khi hoàn tất yêu cầu.
- Sử dụng ngữ cảnh: Trong các hàm dịch vụ và chính sách, thay vì truyền ngữ cảnh như trước, bạn chỉ cần truy cập vào nó khi cần thiết.
Tips Hiệu Suất
- Giảm thiểu Mã: Việc ngừng truyền ngữ cảnh sẽ giúp mã trở nên ngắn gọn hơn, dễ đọc hơn.
- Kiểm Tra Dễ Dàng: Nhờ vào việc giảm số lượng tham số truyền qua, việc kiểm tra cũng trở nên dễ dàng hơn.
Cảnh Báo Thường Gặp
- Đảm bảo rằng biến ngữ cảnh được thiết lập đúng cách trong middleware để tránh lỗi khi không có ngữ cảnh.
- Kiểm tra kỹ các quyền truy cập trước khi thực hiện bất kỳ hành động nào dựa trên ngữ cảnh.
Kết Luận
Sau khi áp dụng mô hình này:
- Giảm 40% mã trong các lớp dịch vụ và kho dữ liệu.
- Thiết lập kiểm tra giảm một nửa, không cần chuỗi mô phỏng phức tạp.
Cảm ơn DHH đã truyền cảm hứng cho giải pháp này! Nếu bạn muốn tìm hiểu thêm, hãy theo dõi blog của tôi để nhận thêm những thông tin và hướng dẫn thú vị khác!