Skip to content

Facades

A facade is a short, static-looking way to reach a service in the container. Instead of resolving the cache by hand and calling it, you write:

from arvel import Cache

await Cache.put("greeting", "hello", ttl=60)
value = await Cache.get("greeting")

Cache here is not the cache. It's a facade — a thin proxy whose every call is forwarded to the real cache service, resolved from the current application's container the moment you call it. You get a terse, readable call site without the thing you'd normally trade for it: because the facade resolves through the container, you keep the testability of dependency injection, and you can swap the backing service for a fake in a test without touching the calling code.

This page covers how the proxying works, the full set of facades, how they stay type-checked, how to fake one in a test, and when to inject the underlying service instead. Facades are part of the core — nothing to install.

How facades work

Every facade extends a base Facade and declares exactly one thing: the container key it stands in for, its accessor. When you call a method on the facade, it resolves that key from the current application and forwards the call:

from arvel.support.facades import Facade

class Cache(Facade):
    @classmethod
    def accessor(cls) -> str:
        return "cache"      # Cache.get(...) → app.make("cache").get(...)

The key word is current. The facade doesn't hold a reference to a cache — it looks the service up on every access, against whatever application is bootstrapped right now. That indirection costs a dictionary lookup and buys you two things: a facade always reflects the live application, and swapping the backing service (a fake, in a test) is transparent to every call site.

Facade class reference

Import any of them straight from the package root — from arvel import Cache, Auth, DB:

Facade Resolves to Facade Resolves to
Config configuration store Cache cache repository
Route the router DB database connections
Auth authentication Gate authorization
Storage file storage Queue job queue
Event event dispatcher Mail mailer
Log logger Hash password hashing
Crypt encrypter Http HTTP client
View view factory Lang translator
Date date factory Validator validator factory
Redis Redis connection Schedule task scheduler
RateLimiter rate limiter

Each proxies one container service — Cache the cache repository, Queue the job queue, and so on. The method surface you call on a facade is exactly the surface of the service behind it, which is why each facade's methods are documented on that feature's own page (Cache, Queues, …) rather than duplicated here.

Typed by stubs

A facade resolves its methods dynamically, which a type checker can't follow on its own — to mypy, Cache.get is just an attribute access on a class that doesn't define get. So arvel ships a generated type stub (arvel/support/facades/__init__.pyi) that declares each facade's methods with their real signatures. Your editor autocompletes Cache.get(...), and mypy and pyright check the call, even though the method only exists at runtime.

The stub is generated from the live services (make stubs), not written by hand — so it can't drift from the real API. Add a method to the cache repository, regenerate, and the facade's type picks it up. This is the detail that makes facades safe in a typed codebase: the ergonomics of a dynamic proxy with the checking of a real method call.

Faking a facade in a test

Here's the payoff that a bare app.make("cache") wouldn't give you. Because the facade resolves its service every time, a test can swap that service for an in-memory fake and then assert against it — no real backend, no network, no cleanup:

from arvel import Cache

def test_dashboard_caches_stats():
    fake = Cache.fake()          # swap the resolved "cache" for an in-memory double
    build_dashboard()            # code under test calls Cache.put(...) as normal
    assert fake.has("stats")     # assert it populated the cache

Cache.fake() replaces the backing service for the duration of the test and hands you the fake to assert on. When you need to substitute something more specific, Cache.swap(obj) puts any object you like behind the facade. Resolved roots reset on application boot, so tests don't leak into each other.

When to use facades

A facade is convenient precisely because it hides a dependency — and that's also its cost. In a long-lived service class, a hidden dependency is a liability: the class's constructor no longer tells you what it needs, and the unit test has to reach for the facade's fake instead of just passing a double. There, inject the underlying contract:

from arvel.cache import CacheRepository

class ReportService:
    def __init__(self, cache: CacheRepository):   # explicit, autowired from the container
        self.cache = cache

Now the dependency is in the signature, and the test constructs ReportService(FakeCache()) with no facade machinery at all.

The rule of thumb: reach for a facade in routes, controllers, and quick scripts — code that's short-lived and read top-to-bottom, where the terseness wins and the hidden dependency doesn't hurt. Inject the contract in service classes — code that lives a long time and deserves an honest constructor. Both resolve from the same container, so mixing them is never a conflict; it's just picking the right tool for the layer.

Common mistakes & gotchas

  • Using a facade before an app exists. A facade resolves against the current application, so calling one before an app is bootstrapped — or outside the app entirely — raises a "no application" error. In a test, build the app first.
  • Reaching for a facade where injection reads better. If a class's whole job is the cached (or queued, or mailed) thing, an injected contract states that plainly and makes the unit test obvious.
  • Forgetting to regenerate stubs. Add a facade, or change a backing service's surface, and rerun the stub generator — otherwise the static types quietly lag the runtime.

See also

  • Service Container — what a facade resolves from, and the injection alternative.
  • Service Providers — where the services facades proxy get registered.
  • Testing — faking facades and services in the test harness.