# Design pattern trong Odoo: Facade

> Mẫu thiết kế Facade giúp che giấu một thư viện phức tạp sau một interface đơn giản như thế nào, và cách lớp Proxy cùng đối tượng Response của Odoo áp dụng mẫu này với Werkzeug.

**Source:** <https://trobz.com/vi/insights/design-patterns-odoo-facade/>

---


Khi phát triển cho Odoo, đặc biệt với các module lớn, lập trình viên thường phải dùng thư viện bên ngoài để mở rộng chức năng. Những thư viện này có thể phức tạp và hiếm khi khớp tự nhiên với framework Odoo, dễ dẫn đến code phụ thuộc chặt chẽ và khó bảo trì.

Mẫu thiết kế Facade giúp giải quyết vấn đề này bằng cách bọc thư viện đó sau một interface đơn giản, phù hợp với Odoo. Bài viết này giới thiệu cách mẫu thiết kế vận hành và cách Odoo áp dụng nó khi tích hợp với bên ngoài.

## Tổng quan về mẫu thiết kế

Trước hết là vấn đề mà mẫu thiết kế giải quyết. Hãy hình dung một thư viện email marketing mạnh nhưng phức tạp. Để gửi một chiến dịch, có thể phải khởi tạo một đối tượng xác thực, một trình quản lý kết nối, một bộ phân tích template, một trình xử lý danh sách người đăng ký và một đối tượng gửi, rồi gọi các phương thức của chúng theo đúng thứ tự. Code phía client khi đó phụ thuộc chặt vào độ phức tạp bên trong của thư viện, và mỗi khi thư viện thay đổi, có thể phải sửa code ở nhiều nơi.

Mẫu thiết kế này đưa ra một lớp Facade duy nhất để đóng gói toàn bộ độ phức tạp đó, với các phương thức đơn giản như `sendCampaign(template, list)`. Bên trong, lớp Facade thực hiện mọi bước cần thiết (khởi tạo đối tượng, gọi phương thức và điều phối luồng xử lý), còn client chỉ làm việc với interface đơn giản. Lớp này đóng vai trò như một "mặt tiền" che đi phần kết nối rối rắm phía sau.

Facade và Proxy thường bị nhầm lẫn, vì cả hai đều là lớp bọc chuyển giao công việc cho một đối tượng khác. Nhưng mục đích của chúng khác nhau:

- **Proxy** có cùng interface với đối tượng mà nó bọc. Mục đích là kiểm soát quyền truy cập vào đối tượng đó.
- **Facade** có interface khác và đơn giản hơn. Mục đích là đơn giản hóa một hệ thống con phức tạp.

Tóm lại, Proxy hướng đến kiểm soát truy cập với interface giữ nguyên, còn Facade hướng đến đơn giản hóa với interface mới. Để tìm hiểu thêm về mẫu Proxy, xem [bài viết trước]({{< surl >}}/vi/insights/design-patterns-odoo-proxy/).

## Odoo áp dụng mẫu thiết kế này như thế nào

Facade là một lớp mới bọc quanh hệ thống con phức tạp. Các phương thức của lớp này chuyển lời gọi đến từng phần của hệ thống con và điều phối cách chúng tương tác.

Odoo cung cấp một [lớp `Proxy`](https://github.com/odoo/odoo/blob/23a99e10b8de56c188f286ed31323f2c5dcd4b4f/odoo/tools/facade.py#L104) tổng quát, "delegates to an underlying instance while exposing a curated subset of its attributes and methods", tức là chuyển giao cho một đối tượng bên dưới nhưng chỉ để lộ một tập thuộc tính và phương thức được chọn lọc. Vì sao lại đặt tên là Proxy khi hai mẫu thiết kế khác nhau? Vì chúng không loại trừ nhau: như docstring của lớp này ghi rõ, cùng một lớp bọc có thể vừa kiểm soát truy cập vừa đơn giản hóa interface.

```python
class Proxy(metaclass=ProxyMeta):
    """
    A proxy class implementing the Facade pattern.

    This class delegates to an underlying instance while exposing a curated subset of its attributes and methods.
    Useful for controlling access, simplifying interfaces, or adding cross-cutting concerns.
    """
    _wrapped__ = object

    def __init__(self, instance):
        """
        Initializes the proxy by setting the wrapped instance.

        :param instance: The instance of the class to be wrapped.
        """
        object.__setattr__(self, "_wrapped__", instance)

    @property
    def __class__(self):
        return type(self)._wrapped__
```

Đi kèm lớp `Proxy` là hai lớp hỗ trợ `ProxyAttr` và `ProxyFunc`, giúp dễ dàng bọc một đối tượng và chỉ để lộ một phần chức năng cụ thể, đã được đơn giản hóa.

Một ví dụ là [lớp `Response` của Odoo](https://github.com/odoo/odoo/blob/23a99e10b8de56c188f286ed31323f2c5dcd4b4f/odoo/http.py#L1634). Tầng web của Odoo được xây dựng trên thư viện Werkzeug, và một đối tượng `werkzeug.wrappers.Response` nguyên bản có thể khá phức tạp. Odoo đơn giản hóa nó bằng lớp `Response` riêng, đóng vai trò Facade:

```python
class Response(Proxy):
    _wrapped__ = _Response  # Odoo's subclass of werkzeug.wrappers.Response

    # werkzeug.wrappers.Response attributes
    add_etag = ProxyFunc(None)
    cache_control = ProxyAttr(ResponseCacheControl)
    # ...

    # odoo.http._response attributes
    qcontext = ProxyAttr()
    template = ProxyAttr(str)
    render = ProxyFunc()
    flatten = ProxyFunc(None)
    # ...
```

Lớp `Response` của Odoo bọc một đối tượng response của Werkzeug và đơn giản hóa interface: thay vì để lộ mọi phương thức của Werkzeug, lớp này chỉ để lộ một tập được chọn lọc, như `set_cookie` và `status_code`. Lớp này còn bổ sung các khái niệm riêng của Odoo mà đối tượng Werkzeug bên dưới không hề biết đến, chẳng hạn render trực tiếp template QWeb qua `template`, `qcontext` và `render()`.

Nhờ đó, tầng HTTP của Odoo có thể làm việc với đối tượng đơn giản này thay vì phải thiết lập Werkzeug đầy đủ. Ví dụ, trang hiển thị sau khi người dùng bên ngoài đăng nhập thành công:

```python
@http.route('/web/login_successful', type='http', auth='user', website=True, sitemap=False)
def login_successful_external_user(self, **kwargs):
    """Landing page after successful login for external users (unused when portal is installed)."""
    valid_values = {k: v for k, v in kwargs.items() if k in LOGIN_SUCCESSFUL_PARAMS}
    return request.render('web.login_successful', valid_values)
```

Chỉ một lời gọi `request.render()`, Facade đã lo phần render template và dựng HTTP response phía sau.

## Ưu và nhược điểm

**Ưu điểm:**

- **Giảm phụ thuộc**: code phía client được tách khỏi cách vận hành bên trong của một hệ thống con phức tạp, nên chịu được thay đổi tốt hơn.
- **Đơn giản**: hệ thống con dễ dùng hơn nhờ một API cấp cao, dễ hiểu.

**Nhược điểm:**

- **Nguy cơ thành [God object](https://en.wikipedia.org/wiki/God_object)**: nếu không thiết kế cẩn thận, Facade có thể phình to và phụ thuộc vào quá nhiều lớp.
- **Che mất tính năng**: việc đơn giản hóa có thể làm ẩn đi những tính năng hữu ích của hệ thống con bên dưới.

## Kết luận

Facade là một công cụ hữu ích để đơn giản hóa các hệ thống con phức tạp, nhất là khi tích hợp thư viện bên ngoài vào Odoo. Bọc độ phức tạp sau một interface gọn gàng, phù hợp với Odoo giúp lập trình viên viết code dễ bảo trì và dễ đọc hơn. Dù vậy, vẫn cần tránh để Facade quá tải hoặc che đi những tính năng mà người dùng hệ thống con cần đến.

## Tài liệu tham khảo

- Refactoring.Guru, [Facade](https://refactoring.guru/design-patterns/facade).
- Bran van der Meer (2023), [The Simplest Design Pattern: Facade](https://www.youtube.com/watch?v=9tYHxA9HchI), YouTube.

