How the Facade pattern hides a complex library behind a simple interface, and how Odoo's Proxy class and Response object apply it to Werkzeug.

When developing for Odoo, especially in large modules, you often need external libraries to extend functionality. These libraries can be complex and rarely fit naturally with the Odoo framework, which tends to lead to tightly coupled code that is hard to maintain.

The Facade pattern helps with this. It wraps such a library behind a simple, Odoo-friendly interface. Let’s look at how the pattern works and how Odoo uses it for external integrations.

Overview of the Pattern

First, the problem the pattern solves. Imagine a powerful but complicated library for email marketing. To send a single campaign, you might need to create an authenticator, a connection manager, a template parser, a subscriber list handler and a sender object, then call their methods in a specific order. Your client code becomes tightly coupled to the library’s internal complexity, and when the library changes, you may have to update your code in many places.

The pattern introduces a single Facade class that encapsulates that complexity and offers simple methods such as sendCampaign(template, list). Internally, the Facade performs all the necessary steps (creating objects, calling methods and managing the workflow), while the client only deals with the simple interface. It acts as a facade that hides the wiring behind it.

The Facade and Proxy patterns are often confused, because both are wrappers that delegate work to another object. Their intent is different:

  • A Proxy has the same interface as the object it wraps. Its purpose is to control access to that object.
  • A Facade has a different, simpler interface. Its purpose is to simplify a complex subsystem.

In short, a Proxy is about access control with a matching interface, and a Facade is about simplification with a new interface. For more on the Proxy pattern, see our previous post .

How Odoo Implements the Pattern

A Facade is a new class that wraps the complex subsystem. Its methods delegate calls to the various parts of the subsystem and orchestrate how they interact.

Odoo provides a generic Proxy class that “delegates to an underlying instance while exposing a curated subset of its attributes and methods.” Why call it a Proxy if the two patterns are different? Because they are not mutually exclusive: as its docstring says, the same wrapper can control access and simplify an interface.

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__

Alongside Proxy, the helpers ProxyAttr and ProxyFunc make it easy to wrap an object and expose only a specific, simplified subset of its functionality.

One example is Odoo’s Response class . Odoo’s web layer is built on the Werkzeug library, and a raw werkzeug.wrappers.Response object can be complex. Odoo simplifies it with its own Response class, which acts as a Facade:

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)
    # ...

The Odoo Response class wraps a Werkzeug response object and simplifies its interface: instead of exposing every Werkzeug method, it exposes a curated set, such as set_cookie and status_code. It also adds Odoo-specific concepts that the underlying Werkzeug object knows nothing about, such as rendering a QWeb template directly through template, qcontext and render().

Odoo’s HTTP layer can then work with this simpler object instead of a full Werkzeug setup. For example, the landing page after login for external users:

@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)

This single request.render() call relies on the Facade to handle template rendering and HTTP response construction behind the scenes.

Pros and Cons

Pros:

  • Decoupling: client code is separated from the internal workings of a complex subsystem, which makes it more resilient to change.
  • Simplicity: the subsystem is easier to use through a straightforward, high-level API.

Cons:

  • God object risk: if it is not designed carefully, a Facade can become bloated and coupled to too many classes.
  • Hidden features: the simplification can hide useful features of the underlying subsystem.

Conclusion

The Facade pattern is a useful tool for simplifying complex subsystems, especially when integrating external libraries into Odoo. Wrapping complexity behind a clean, Odoo-friendly interface helps developers write code that is easier to maintain and read. Care is still needed to avoid overloading the Facade or hiding features that users of the subsystem need.

References