Intent
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use without removing access to it.
The Problem
You're publishing a blog post. To do it correctly you need to:
- Validate the markdown.
- Render it to HTML.
- Generate the URL slug.
- Save the post to the database.
- Invalidate the cache for the listing page.
- Notify email subscribers.
- Push it to your social-media accounts.
Right now your controller calls all seven services directly:
public function publish(Request $request): Response
{
$markdown = $this->validator->check($request->input('body'));
$html = $this->renderer->render($markdown);
$slug = $this->slugger->generate($request->input('title'));
$post = $this->repository->save([...]);
$this->cache->invalidate('blog.listing');
$this->subscribers->notify($post);
$this->social->broadcast($post);
return new Response($post);
}
The controller now imports seven services. Every test that touches "publish" mocks seven things. A new dev needs to learn all seven names just to understand what happens when someone hits "Publish." When you add an eighth concern (search-index update), you edit every publish-related controller.
The complexity hasn't gone anywhere — it's just leaked into the caller.
The Solution
Facade says: introduce one class, BlogPublisher, with one method, publish($post). Inside, it orchestrates the seven services. The controller goes from importing seven to importing one. Callers see one thing and don't care how many parts are inside.
final class BlogPublisher
{
public function __construct(
private MarkdownValidator $validator,
private MarkdownRenderer $renderer,
private Slugger $slugger,
private PostRepository $repository,
private CacheManager $cache,
private SubscriberMailer $subscribers,
private SocialBroadcaster $social,
) {}
public function publish(string $title, string $body): Post
{
$markdown = $this->validator->check($body);
$html = $this->renderer->render($markdown);
$slug = $this->slugger->generate($title);
$post = $this->repository->save(compact('title', 'html', 'slug'));
$this->cache->invalidate('blog.listing');
$this->subscribers->notify($post);
$this->social->broadcast($post);
return $post;
}
}
The controller's body shrinks to return $this->publisher->publish($title, $body);. The complexity didn't disappear — it moved into one focused class whose entire job is coordinating publication. Adding the eighth step is a one-line change inside the facade.
Real-World Analogy
A hotel concierge. To plan an evening out you'd otherwise need to: call the airline to time a return, reserve the rental car, book the restaurant, find theatre tickets in your seating preference, and arrange a taxi back. That's seven calls and seven phone numbers.
Or: you can walk up to the concierge and say, "I'd like an evening out tomorrow — dinner around seven, then theatre." They coordinate the whole thing. They didn't replace the airline, the rental car, the restaurant, the theatre, or the taxi — those still exist behind them. They just gave you one entry point so you didn't have to talk to all of them.
Structure
Three roles you'll see in every Facade implementation:
- Facade — the simplifying class. Exposes one (or a few) high-level methods. Knows about every subsystem class it coordinates. Here:
BlogPublisher. - Subsystem classes — the existing services that do the actual work. They don't know the Facade exists; they're just used by it. Here:
MarkdownValidator,PostRepository,CacheManager, etc. - Client — the caller. Depends only on the Facade. Free to bypass it and call subsystems directly when it needs to.
The defining property: subsystems remain accessible. Facade doesn't block direct access — it's just a convenient default for callers who want the orchestrated flow.
Code Examples
Here's a BlogPublisher facade in five languages. Watch the controller shrink to one call — and the orchestration logic settle into one focused class.
export class BlogPublisher {
constructor(
private validator: MarkdownValidator,
private renderer: MarkdownRenderer,
private slugger: Slugger,
private repository: PostRepository,
private cache: CacheManager,
private subscribers: SubscriberMailer,
private social: SocialBroadcaster,
) {}
async publish(title: string, body: string): Promise<Post> {
const markdown = this.validator.check(body);
const html = this.renderer.render(markdown);
const slug = this.slugger.generate(title);
const post = await this.repository.save({ title, html, slug });
await this.cache.invalidate("blog.listing");
await this.subscribers.notify(post);
await this.social.broadcast(post);
return post;
}
}
// In the controller:
// return this.publisher.publish(req.body.title, req.body.body);
class BlogPublisher:
def __init__(self, validator, renderer, slugger, repository,
cache, subscribers, social):
self.validator = validator
self.renderer = renderer
self.slugger = slugger
self.repository = repository
self.cache = cache
self.subscribers = subscribers
self.social = social
def publish(self, title, body):
markdown = self.validator.check(body)
html = self.renderer.render(markdown)
slug = self.slugger.generate(title)
post = self.repository.save(title=title, html=html, slug=slug)
self.cache.invalidate("blog.listing")
self.subscribers.notify(post)
self.social.broadcast(post)
return post
# In the controller:
# return publisher.publish(request.title, request.body)
public final class BlogPublisher {
private final MarkdownValidator validator;
private final MarkdownRenderer renderer;
private final Slugger slugger;
private final PostRepository repository;
private final CacheManager cache;
private final SubscriberMailer subscribers;
private final SocialBroadcaster social;
public BlogPublisher(MarkdownValidator validator, MarkdownRenderer renderer,
Slugger slugger, PostRepository repository,
CacheManager cache, SubscriberMailer subscribers,
SocialBroadcaster social) {
this.validator = validator;
this.renderer = renderer;
this.slugger = slugger;
this.repository = repository;
this.cache = cache;
this.subscribers = subscribers;
this.social = social;
}
public Post publish(String title, String body) {
String markdown = validator.check(body);
String html = renderer.render(markdown);
String slug = slugger.generate(title);
Post post = repository.save(title, html, slug);
cache.invalidate("blog.listing");
subscribers.notify(post);
social.broadcast(post);
return post;
}
}
<?php
namespace App\Blog;
final class BlogPublisher
{
public function __construct(
private MarkdownValidator $validator,
private MarkdownRenderer $renderer,
private Slugger $slugger,
private PostRepository $repository,
private CacheManager $cache,
private SubscriberMailer $subscribers,
private SocialBroadcaster $social,
) {}
public function publish(string $title, string $body): Post
{
$markdown = $this->validator->check($body);
$html = $this->renderer->render($markdown);
$slug = $this->slugger->generate($title);
$post = $this->repository->save(['title' => $title, 'html' => $html, 'slug' => $slug]);
$this->cache->invalidate('blog.listing');
$this->subscribers->notify($post);
$this->social->broadcast($post);
return $post;
}
}
package blog
type BlogPublisher struct {
Validator *MarkdownValidator
Renderer *MarkdownRenderer
Slugger *Slugger
Repository *PostRepository
Cache *CacheManager
Subscribers *SubscriberMailer
Social *SocialBroadcaster
}
func (p *BlogPublisher) Publish(title, body string) (*Post, error) {
markdown, err := p.Validator.Check(body)
if err != nil {
return nil, err
}
html := p.Renderer.Render(markdown)
slug := p.Slugger.Generate(title)
post, err := p.Repository.Save(title, html, slug)
if err != nil {
return nil, err
}
p.Cache.Invalidate("blog.listing")
p.Subscribers.Notify(post)
p.Social.Broadcast(post)
return post, nil
}
The shape is identical across languages: the facade carries the seven dependencies; callers see only publish(). The complexity is contained, not eliminated — but containment is itself a feature.
When to Use It
Reach for Facade when you can answer "yes" to any of these:
- Multiple callers do the same multi-step orchestration. Three controllers each call seven services in the same order — that order belongs in one place.
- You want a single entry point to a library or module. When you publish a package, the Facade is what's documented and what most callers use; the subsystem is for power users who need finer control.
- You want to limit coupling between layers. A presentation layer that talks to one service-layer Facade is decoupled from the dozen domain services behind it.
- You're integrating with a complex third-party SDK. The SDK's surface area is huge; you only use a slice of it. A Facade exposes just the slice you use in your project's vocabulary.
- A new dev should be able to use the subsystem in five minutes. If the answer requires reading seven READMEs, you need a Facade.
If you only ever have one or two subsystem calls and they happen in one place, a Facade just adds a layer. The pattern earns its cost when the orchestration recurs or when the surface area is intimidating.
Pros and Cons
Pros
- One method to learn instead of seven.
- Tests become tractable — mock the Facade, not its dependencies.
- The orchestration order lives in one place; bug fixes have one home.
- New subsystem concerns can be added (audit logging, metrics) without touching every caller.
- Subsystems remain accessible for power users — Facade doesn't block, it just defaults.
Cons
- Risk of becoming a god class. A Facade with twenty methods coordinating fifteen subsystems isn't a Facade anymore — it's a god class with a polite name. Watch for growth.
- Hides complexity that sometimes matters. Callers who need fine-grained control may find the Facade too coarse and fall back to subsystem calls — at which point they need to learn the subsystem anyway.
- Easy to over-broaden. A "publish post" Facade that also handles "draft post" and "schedule post" and "edit post" is on its way to becoming "PostManager." Each operation deserves its own focused Facade.
- Adds a layer of indirection. When you're debugging "why didn't subscribers get the email?" you have an extra step to trace through.
Pro Tips
- One Facade per workflow, not per subsystem.
BlogPublisheris right;BlogService(which would also handle reading, listing, tagging, etc.) is the slope to a god class. - Keep the Facade thin. It coordinates calls; it doesn't add business logic. If you find yourself writing significant logic in the Facade, that logic is its own concern and wants its own home.
- Don't restrict subsystem access. Make subsystem classes public; let direct access be a power-user escape hatch. The Facade is a default, not a wall.
- Inject subsystem dependencies. A Facade that constructs its own subsystems (
new MarkdownValidator()) is unmockable. Take them in the constructor. - Watch for "Facade + Singleton." A common anti-pattern is making the Facade global. Once it's global, every caller is hard-coupled to it; the testability advantage you just gained is gone. Inject the Facade like any other service.
Relations with Other Patterns
- Adapter translates one interface into another; Facade simplifies many interfaces into one. Adapter is about shape mismatch; Facade is about complexity reduction.
- Proxy wraps one object with the same interface to control access; Facade wraps several objects with a new interface to simplify use.
- Mediator also coordinates between objects, but bidirectionally — colleagues talk to each other through the mediator. Facade is one-way: client calls Facade calls subsystems.
- Singleton sometimes co-occurs with Facade — there's only one publisher in the app — but resist the temptation to enforce that with a literal Singleton. Let the DI container manage the lifetime.
Final Tips
The Facade pattern is the one I reach for most often when joining an existing codebase. If a controller imports a dozen services and the same orchestration recurs across handlers, the cleanest first refactor is almost always to extract a Facade — and the test surface immediately gets smaller, the controllers get readable, and the next dev who joins gets to learn one method instead of twelve.
The pattern is humble. It doesn't restructure your code; it just gives complexity a polite door to enter through. Reach for it the moment you notice yourself thinking, "to do X, you need to call A, then B, then C..." — that sentence is asking for a Facade.


