In this article I’ll be going over one of my most used design patterns called the facade pattern (or façade pattern), and explaining why I like to use it. As with all things I share information about, it’s important to remain pragmatic as software engineers.
With that said, this article is not to persuade you to use this design pattern exclusively or that there are no alternatives to the facade pattern. Instead, I’d like to arm you with another design pattern tool in your figurative coding toolbox. The more tools you have available, the better prepared you are to build awesome stuff.
Goals of the Facade Pattern
When I am developing software, whether it is personally, professionally, or as someone who is influencing the direction of software I am not directly coding myself, I encourage a focus on flexibility in software. I have seen too many instances of situations where codebases reached a point where people just said:
“Well, we need to rewrite it because it will never support X”
A developer
Unfortunately, I know I will see this many more times in my career. A lot of the time this occurs because a codebase is littered with tight coupling to a specific implementation. Over time, this coupling grew so strong that when a pivot needed to happen it seemed impossible to address it without starting over.
Now, a facade pattern doesn’t magically fix this for us. It’s not a silver bullet. But a facade is a tool that we can leverage to help hide away implementation details or other complexities and put a friendly API on that we’re happy to work with. We can also reach a point where we have extensible systems that add functionality or data sources without having to modify the core code to support it.
Sound cool? Let’s jump in.
A Facade Pattern Video!
Example of a Facade Pattern
I want to use an example to walk through how could might evolve to the point where a facade design pattern can demonstrate some effectiveness. For this example, you can find the code on GitHub before we use facades and below:
Console.WriteLine("Starting example 1...");
var data = new Data(
"Dev Leader Website",
"https://www.devleader.ca");
var originalEmailBasedDataPublisher = new OriginalEmailBasedDataPublisher();
await originalEmailBasedDataPublisher.PublishAsync(
"https://smtpserver",
"SomeUsername",
"SecretPassword123",
data);
Console.WriteLine("Example 1 complete.");
sealed record Data(
string Name,
string Value);
sealed class OriginalEmailBasedDataPublisher
{
public Task PublishAsync(
string smtpServer,
string smtpUsername,
string smtpPassword,
Data data)
{
// TODO: go actually send some email... this is just to demo
Console.WriteLine($"Sending email for '{data}'...");
return Task.CompletedTask;
}
}
In the example code above, we have a simple data publisher that is able to take a piece of data and publish it out. It’s not implemented (because it doesn’t matter for this context), but the idea is that this specific implementation is email-based publishing and that it needs some configuration specific to email.
As the app grows, you find that you now need to publish data to two spots. Not only that but where you’re publishing data from in your code has grown from one location to several. So to implement this next change, you need to go to multiple spots in your code base and add code like the following example:
Console.WriteLine("Starting example 2...");
var data = new Data(
"Dev Leader Website",
"https://www.devleader.ca");
var originalEmailBasedDataPublisher = new OriginalEmailBasedDataPublisher();
var originalSmsBasedDataPublisher = new OriginalSmsBasedDataPublisher();
// this is the code that gets dropped into multiple spots across your application:
await originalEmailBasedDataPublisher.PublishAsync(
"https://smtpserver",
"SomeUsername",
"SecretPassword123",
data);
await originalSmsBasedDataPublisher.PublishAsync(
new SmsConfiguration("Some Config Details"),
data);
Console.WriteLine("Example 2 complete.");
If we continue this pattern, assuming over time our code base is growing larger and larger it’s likely that two things continue:
- We keep adding more publishers that have their own criteria for being able to publish
- We keep adding more spots to the code base that need to be maintained every time we need to change, add, or remove a publisher
So what can we do?
Refactor with a Facade API
Like with most things, there are going to be multiple ways to make this code feel more scalable and flexible. This is regardless of whether or not we want to use a facade pattern here. And even with my suggestions here for converting to a facade pattern, you could still do this in many different ways and still have what feels like a facade over your implementations.
The first step I like to take is thinking about what I want the facade API to look like. This often comes a lot (relatively) naturally to me when I am writing my own applications because I use facades very regularly. In this case, I’d suggest we look at what we need to provide per PublishAsync
that is not unique to any one implementation. If we look across the two existing implementations, it looks like the only common parameter exists which will be the Data
record. Additionally, both implementations are async Task
without a specific return value.
Sometimes if we try to refactor without seeing enough examples, we can create an API that will not be extensible enough for other implementations. I generally like to lean on the “Rule of Three” for this sort of thing so that I’ve seen at least three implementations before I try refactoring. However, in this case we can create our API such that we have the one Data
parameter and common async Task
return type.
This leaves us with an API that looks like the following:
interface IDataPublisher
{
Task PublishAsync(Data data);
}
Implementing the Facade Dependencies
It’s important to note that the API we defined previously is what we will see throughout the code that our facade will implement. The existing implementations for email and SMS do not have to meet this API as we are able to call their specific implementations in one location in our code within the facade. However, in this example we can actually move these implementation-specific parameters to constructor parameters for these objects. This may not work in all cases, but for our example, these configurations can safely exist for the lifetime of the object instances we have.
Our email implementation can switch to the following code:
sealed class EmailBasedDataPublisher : IDataPublisher
{
public EmailBasedDataPublisher(
string smtpServer,
string smtpUsername,
string smtpPassword)
{
// NOTE: we pass in implementation-specific
// configuration via constructor now
}
public Task PublishAsync(Data data)
{
// TODO: go actually send some email... this is just to demo
Console.WriteLine($"Sending email for '{data}'...");
return Task.CompletedTask;
}
}
Our SMS implementation can switch to the following code:
sealed class SmsBasedDataPublisher : IDataPublisher
{
public SmsBasedDataPublisher(SmsConfiguration configuration)
{
// NOTE: we pass in implementation-specific
// configuration via constructor now
}
public Task PublishAsync(Data data)
{
// TODO: go actually send some email... this is just to demo
Console.WriteLine($"Sending SMS for '{data}'...");
return Task.CompletedTask;
}
}
Implementing the Facade
We can actually write our facade to be agnostic to the email and SMS dependencies and this is because we took one extra step. By making the implementations share a common API, the facade doesn’t need to do anything special for either of them. However, one of the main drivers of having a facade is to hide complexity and put it in a common spot. So if you needed to call each implementation explicitly within the facade, that could still be an improvement over the original code.
Here’s what our facade looks like when the dependencies also match the same API as the facade itself:
sealed class PublisherFacade : IDataPublisher
{
private readonly IReadOnlyCollection<IDataPublisher> _publishers;
public PublisherFacade(IEnumerable<IDataPublisher> publishers)
{
_publishers = publishers.ToArray();
}
public async Task PublishAsync(Data data)
{
// NOTE: this could be implemented in many different ways...
var publishTasks = _publishers.Select(x => x.PublishAsync(data));
await Task.WhenAll(publishTasks);
}
}
If we look at the `PublishAsync` method on the facade, you can see that I left a comment about how we could have flexibility in the implementation. Would you like the facade to call each dependent implementation serially? In parallel? Do we need to order the execution of each of the dependencies we are calling?
There are plenty of decisions to make here. If you think back to the initial example this stemmed from, now that logic is in one spot and not across your code base.
Putting It All Together
Let’s have a look at this code example that instantiates and calls our facade:
Console.WriteLine("Starting example 3...");
var emailPublisher = new EmailBasedDataPublisher(
"https://smtpserver",
"SomeUsername",
"SecretPassword123");
var smsPublisher = new SmsBasedDataPublisher(
new SmsConfiguration("Some Configuration"));
var publisher = new PublisherFacade(new IDataPublisher[]
{
emailPublisher,
smsPublisher
});
var data = new Data(
"Dev Leader Website",
"https://www.devleader.ca");
await publisher.PublishAsync(data);
Console.WriteLine("Example 3 complete.");
In the code above, almost every line is just setting up our facade and dependencies. The two lines towards the end are setting up a Data
record and actually calling the PublishAsync
method of our facade. If we contrast this to the direction the initial code was headed in:
- We’ve now hidden the specific implementation dependencies within our facade. This exists in one spot, instead of each location we want to publish data.
- If we had special logic for the ordering of execution for each publisher, this is now contained within one spot instead of each spot in the code base that wanted to publish data.
- If we want to add more data publishers, none of our code calling
PublishAsync
needs to change. We can extend the system functionality by providing an additional implementation to the facade.
What’s Next?
Hopefully, this overview gave you some ideas for how you can leverage the facade pattern and you incorporate it with your other design patterns! I find them tremendously valuable for hiding complexity and allowing my systems to be extended by injecting new dependencies into the facade.
With that said, you may be interested in looking into some plugin loading with Autofac and Reflection! Plugin architectures work very well with the facade design pattern and it’s a great example of how multiple design patterns can work synergistically!