Introduction
The facade pattern is used as a way to hide more complex logic. A facade can do the following:
- Improve usability of a library or API by masking interaction with the more complex components of the underlying code.
- Provide interfaces which are context-specific. That means that each client of an API could get its own facade, depening on use-cases.
A facade pattern is typically used in the following cases:
- A subsystems, or more subsystems have tightly coupled elements.
- In layered software, you might need an entry point for each layer.
- In the case of very complex systems, a facade can help
This might all sound quite complex, here is what it looks like:

The Facade class has a simple interface hiding the fact that it might have more complex interaction with for example classA and classB. This example is simple, but you can imagine that in complex systems, there are tens or hundreds of classes with which the Façade has to interact.
Implementation in Python
In this, rather contrived, example, we will build an imaginary ingester library, which ingest data in three different ways: through RSS, Mail or manually.
We will start by importing some things:
from abc import ABC, abstractmethod
Why do we need these?
ABCstands for ‘Abstract Base Class’ which is a class which can contain abstract methods, that is methods which will be fully implement in a subclass of this class.- The
@abstractmethodattribute flags a method as abstract, that is it has no implementation.
All of the ingesters get the same interface. Because of the flexibility of this pattern this is not a necessity, but I do it here to keep things simple:
class Ingester(ABC):
@abstractmethod
def ingest(self, data: str):
pass
Some notes:
- The
Ingesterclass derives fromABCmeaning it can contain abstractmethods. - It contains exactly one abstract method, denoted by the
@abstractmethodattribute, calledingest()
Now we define our first ingester:
class RSSIngester(Ingester):
def ingest(self, data: str):
print(f"RSS Ingester: Ingesting RSS data: {data}")
We can be very short about this class:
- It derives from the Ingester class, which means it needs to define the ingest() method
- The ingest() method does nothing more than just print out the data.
In the same way we can define the other ingestors:
class MailIngester(Ingester):
def ingest(self, data: str):
print(f"Mail Ingester: Ingesting Mail data: {data}")
class ManualIngester(Ingester):
def ingest(self, data: str):
print(f"Manual Ingester: Ingesting Manual data: {data}")
As you can see, they basically follow the same pattern.
Now we come to the interesting bit: the Facade class itself:
class Facade:
def __init__(self):
self._rss_ingester = RSSIngester()
self._mail_ingester = MailIngester()
self._manual_ingester = ManualIngester()
def ingest_from_source(self, source_type: str, data: str):
if source_type == "rss":
self._rss_ingester.ingest(data)
elif source_type == "mail":
self._mail_ingester.ingest(data)
elif source_type == "manual":
self._manual_ingester.ingest(data)
else:
raise ValueError(f"Unknown source: {source_type}")
def ingest_all(self, data: str):
for ingester_name, ingester in [
("RSS", self._rss_ingester),
("Mail", self._mail_ingester),
("Manual", self._manual_ingester)
]:
try:
ingester.ingest(data)
except Exception as e:
print(f"Failed to ingest via {ingester_name}: {e}")
Here some more explanation is needed:
- In the constructor, we initialize three fields with the three types of ingester
- The
ingest_from_source()method takes both a source_type and data as its parameter, and routes the data to the correct ingester. If no ingester can be found it raises an error. - Sometimes it is practical to call all ingesters from one method, that is what
ingest_all()method does. It loops over each configured ingester, and callsingest()on the provided data. Error are handled by the try-catch block.
This method simply loops over an array containing tuples with the the name of the ingester and the ingester object. In the loop body the ingest() method is called, and errors are handled.
Now we can test the facade:
if __name__ == "__main__":
facade = Facade()
facade.ingest_all("Data to ingest")
facade.ingest_from_source("manual","manual data")
facade.ingest_from_source("http","HTTP Data")
Line by line:
- A Facade object is created
- Next we call
ingest_all()to ingest the data using all ingesters. - Now we call a single ingester.
- Finally we test the error handling.
Conclusion
The Facade pattern is an essential tool for managing complexity by providing a simplified, unified interface to a larger body of code, such as a subsystem or a library. This article demonstrated its practical application by creating an Ingester facade that cleanly abstracts the logic for different data ingestion methods—RSS, Mail, and Manual. By encapsulating these complex interactions within the Facade class, we achieved a significant improvement in the system’s usability and maintainability. Clients no longer need to know the specific details of each ingester, instead interacting with a single, clear entry point, which dramatically decouples the client code from the subsystem’s intricate workings. This pattern is particularly valuable in layered architectures and highly complex systems where tight coupling needs to be avoided. Ultimately, the Facade pattern champions the principle of simplicity, making large-scale systems easier to understand, consume, and evolve over time, as clearly illustrated by the straightforward Python implementation provided.




