Facade (a simple front door over messy steps)
When to use
- You keep writing the same multi-step dance (serialize → compress → hash → upload).
- You want one clean method your code calls, while the Facade handles the gritty details.
- You need consistent metadata, errors, and logging across many call sites.
Avoid when the underlying thing is already simple (one call) or a small helper function is enough.
Diagram (text)
Caller ──> BackupFacade.store_json(...)
├─ json.dumps(...)
├─ gzip.compress(...)
├─ sha256(...)
└─ storage.put(..., metadata={...})
Python example (≤40 lines, type-hinted)
Concrete case: hide JSON encoding, gzip, checksum, and storage metadata behind one call.
from __future__ import annotations
from typing import Protocol, Mapping, Callable
from dataclasses import dataclass
import json, gzip, hashlib
class Storage(Protocol):
def put(self, bucket: str, key: str, data: bytes, *, metadata: Mapping[str, str]) -> None: ...
def gzip_bytes(b: bytes) -> bytes: return gzip.compress(b)
def sha256_hex(b: bytes) -> str: return hashlib.sha256(b).hexdigest()
@dataclass
class BackupFacade:
storage: Storage
make_key: Callable[[str], str] = lambda name: name
def store_json(self, bucket: str, name: str, obj: object) -> str:
raw = json.dumps(obj, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
gz = gzip_bytes(raw)
key = self.make_key(f"{name}.json.gz")
meta = {"Content-Encoding":"gzip","Content-Type":"application/json","ETag":sha256_hex(gz)}
self.storage.put(bucket, key, gz, metadata=meta)
return key
Tiny pytest (cements it)
def test_facade_hides_details(tmp_path):
import gzip, json
class FakeStorage:
def __init__(self): self.items = []
def put(self, b, k, d, *, metadata): self.items.append((b, k, d, metadata))
fs = FakeStorage()
facade = BackupFacade(fs, make_key=lambda n: f"2025-11-06/{n}")
key = facade.store_json("metrics", "events", {"n": 1})
bucket, saved_key, data, meta = fs.items[0]
assert key == "2025-11-06/events.json.gz" and bucket == "metrics"
assert meta["Content-Encoding"] == "gzip" and meta["Content-Type"] == "application/json"
assert json.loads(gzip.decompress(data).decode()) == {"n": 1}
Trade-offs & pitfalls
- Pros: One call site; consistent behavior; fewer leaks of vendor details; easier auditing/tests.
- Cons: Another layer; can hide too much if it becomes a dumping ground.
- Watch out for:
- Overgrown Facade that knows everything—keep the surface small and cohesive.
- Swallowing errors—wrap and re-raise with context; don’t silence.
- Hard-coding vendor bits—use the Storage interface (or your adapters) underneath.
Pythonic alternatives
- A plain helper function if you only need one operation.
- Context managers for setup/teardown (temp files, staging).
- Adapters + Facade together: Adapter normalizes the vendor API; Facade gives a friendly workflow.
- Libraries like fsspec/s3fs/gcsfs already simplify storage—then your Facade becomes very thin.
Mini exercise
Add store_text(bucket, name, text) that sets Content-Type: text/plain and optionally gzips when len(text) > 1MB. Return the final key and whether compression was used. Write a tiny test for both small and large strings.
Checks (quick checklist)
- Facade exposes one simple method per common workflow.
- Internals handle serialization, compression, metadata, and storage consistently.
- No vendor code leaks—Facade talks to an interface (e.g.,
Storage). - Errors are wrapped with context, not swallowed.
- Tests prove callers don’t need to know the messy steps.




