Dependency injection giữ cho code Odoo gắn kết lỏng như thế nào, và phần tích hợp FastAPI của OCA áp dụng nó ra sao qua phụ thuộc odoo_env cùng cơ chế dependency overriding.

Dependency Injection (DI) là một mẫu thiết kế phần mềm cốt lõi, trong đó một thành phần nhận các phụ thuộc của mình từ bên ngoài thay vì tự tạo ra chúng. Nếu bạn từng làm việc với framework OWL của Odoo thì bạn đã thấy khái niệm này vận hành rồi, qua các Service của nó. Cách làm này được ưa chuộng vì nó tạo ra sự gắn kết lỏng, nhờ đó mã nguồn mô-đun hơn, dễ kiểm thử hơn và bớt lặp lại.

Trong bài này, chúng ta sẽ xem lập trình viên Odoo đang tận dụng mẫu thiết kế mạnh mẽ này ra sao trong rest-framework của OCA, thông qua phần tích hợp FastAPI hiện đại của nó.

Vì sao gắn kết lỏng lại tốt?

Gắn kết (coupling) chỉ mức độ phụ thuộc lẫn nhau giữa các phần của mã nguồn, chẳng hạn giữa các module hay các lớp. Gắn kết lỏng (hay gắn kết thấp) là chuẩn mực vàng. Nó có nghĩa là các thành phần hoạt động độc lập được với nhau, và điều đó mang lại những lợi thế rất lớn:

  • Linh hoạt hơn: khi các thành phần không bị buộc chặt vào nhau, lập trình viên sửa hoặc thay một phần mà không gây hiệu ứng lan truyền làm hỏng cả hệ thống. Nhờ vậy việc thích nghi và tiến hóa ứng dụng ngay trong lúc chạy dễ hơn nhiều.
  • Tính mô-đun tốt hơn: thay đổi được khu trú lại, giảm thời gian và nguồn lực cần cho việc phát triển, kiểm thử và bảo trì. Rốt cuộc, điều đó hạ cả chi phí lẫn độ phức tạp chung của dự án.

Dependency Injection trong framework FastAPI của OCA

Phần tích hợp FastAPI được đưa vào thế giới Odoo nhờ PR này. Nó mang các tính năng hiện đại của FastAPI vào Odoo, trong đó có phần hỗ trợ Dependency Injection vừa nguyên bản vừa thanh thoát. Khái niệm này cho phép các hàm khai báo những phụ thuộc chúng cần, còn framework lo việc cung cấp chúng.

Ví dụ cài đặt

Một trong những phụ thuộc hữu dụng nhất là odoo_env. Nó tự động cung cấp môi trường Odoo hiện tại (odoo.api.Environment) cho endpoint của API, thứ thiết yếu để tương tác với các model của Odoo. Ví dụ, để tạo một lead CRM từ một lời gọi API, ta khai báo Depends(odoo_env) trong hàm xử lý route như sau:

@lead_router.post("/leads", status_code=201)
def create(
   data: LeadInput,
   env: Annotated[api.Environment, Depends(odoo_env)],
) -> Lead | None:
   lead = env["crm.lead"].create(data.to_crm_lead_vals())
   return Lead.from_crm_lead(lead)

Nó hoạt động thế nào: điều này làm được là nhờ một biến ContextVar (odoo_env_ctx) được khởi tạo với đúng môi trường Odoo ở đầu mỗi request.

odoo_env_ctx: ContextVar[Environment] = ContextVar("odoo_env_ctx")

Các phụ thuộc hữu ích khác, chẳng hạn phần xác thực, được giải thích rõ ràng tại đây.

Khái niệm liên quan

Một mở rộng mạnh mẽ của ý tưởng này là dependency overriding, do FastAPI đưa ra và rất hữu dụng khi kiểm thử hoặc khi cần tùy biến hành vi một cách linh động. Khái niệm này vào FastAPI qua PR này và cho thấy rõ những lợi thế của mẫu thiết kế đó. Với dependency_override, phần logic dùng chung (như xác thực hay kiểm tra hợp lệ dữ liệu) chỉ cần định nghĩa một lần rồi dùng lại ở nhiều endpoint. Ví dụ, dựa trên trường cấu hình phương thức xác thực, FastAPI quyết định được nên dùng cơ chế xác thực nào.

def _get_app(self):
    app = super()._get_app()
    if self.app == "demo":
    # Here we add the overrides to the authenticated_partner_impl method
    # according to the authentication method configured on the demo app
        if self.demo_auth_method == "http_basic":
            authenticated_partner_impl_override = (authenticated_partner_from_basic_auth_user)
        else:
            authenticated_partner_impl_override = (api_key_based_authenticated_partner_impl)
            app.dependency_overrides[authenticated_partner_impl] = (authenticated_partner_impl_override)
     return app

Đọc thêm