Design Patterns in Odoo: Facade

When developing for Odoo, especially in large modules, you’ll often need to integrate external libraries to extend functionality. These external systems can be complex, rarely aligning naturally with the Odoo framework. This often leads to tightly coupled, hard-to-maintain code.
The Facade pattern helps solve this problem. It provides a clean approach to wrapping such libraries behind a simple, Odoo-friendly interface. Let’s explore how the Facade pattern works and how Odoo leverages it when dealing with external integrations.

Overview of the Pattern

Firstly, let’s answer what problem the pattern solves. Imagine a powerful but complicated library for handling, say, email marketing. To send a single campaign, you might need to instantiate an authenticator, a connection manager, a template parser, a subscriber list handler, and a sender object, and then call methods on them in a specific order. Your client code becomes tightly coupled to the library's internal complexity. If the library is updated, you might have to change your code in many places.

So, what the pattern does is to introduce a single Facade class that encapsulates all the complexity. This class provides simple methods like sendCampaign(template, list). Internally, the Facade class will perform all the necessary steps (instantiating objects, calling methods, and managing the workflow) while the client only interacts with the simple, clean interface. It acts as a "facade" that hides the messy wiring behind it.

The Facade and Proxy patterns are often confused because they are both wrappers that delegate tasks to another object. However, 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.

So, while both wrap another object, 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, check out our previous post.

How to Implement

Implementing a Facade involves creating a new class that wraps the complex subsystem. This class's methods will delegate calls to the various parts of the subsystem, orchestrating their interactions.

In Odoo, a generic Proxy class is used to “delegate to an underlying instance while exposing a curated subset of its attributes and methods.”

Why naming Proxy if Facade and Proxy are different?

In fact, an object can be both at the same time

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__

Along with this Proxy class, there are ProxyAttr and ProxyFunc, which allows Odoo developers to easily 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. However, a raw werkzeug.wrappers.Response object can be complex. Odoo simplifies this with its own Response class, which acts as a Facade.

class Response(Proxy):
    _wrapped__ = _Response # _Response is Odoo's subclass of werkzeug.wrappers.Response
 # Exposing a curated list of Werkzeug's attributes and methods
    add_etag = ProxyFunc(None)
    cache_control = ProxyAttr(ResponseCacheControl)
    # ...

    # Adding Odoo-specific methods
    qcontext = ProxyAttr()
    template = ProxyAttr(str)
    render = ProxyFunc()
    flatten = ProxyFunc(None)
    # ...

Here, the Odoo Response class wraps a Werkzeug response object. It simplifies the Interface, meaning it doesn't expose all of Werkzeug's methods, only necessary ones ( set_cookie, status_code, etc.). On top of that, It introduces higher-level, Odoo-specific concepts like direct QWeb template rendering via the template, qcontext, and render() methods, which the underlying Werkzeug object knows nothing about.

Odoo's HTTP layer can now work with a much simpler object, instead of complex Werkzeug setup.

@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 simple call uses the Facade to handle all the complex template rendering logic and HTTP response construction behind the scenes.

Pros and Cons

Pros:

  • Decoupling: It decouples client code from the complex internal workings of a subsystem. This makes code more resilient to changes.

  • Simplicity: It makes the subsystem easier to use by providing a straightforward, high-level API.

Cons:

  • God Object Risk: If not designed carefully, a Facade can become bloated and coupled to too many classes.
    Hiding Features: The simplification might hide useful features of the underlying subsystem.

Conclusion

The Facade pattern is a powerful tool for simplifying complex subsystems, especially when integrating external libraries into Odoo. By wrapping complexity behind clean, Odoo-friendly interfaces, developers can write more maintainable, resilient, and readable code. However, care must be taken to avoid overloading the Facade or unnecessarily hiding useful features.


Reference

Facade (nd) Refactoring.Guru. Available at: https://refactoring.guru/design-patterns/facade.

van der Meer, B. (2023) The Simplest Design Pattern: Facade , YouTube. Available at: https://www.youtube.com/watch?v=9tYHxA9HchI.