README.md

# Facade

[![Hex.pm](https://img.shields.io/hexpm/v/facade.svg)](https://hex.pm/packages/facade)
[![Hexdocs](https://img.shields.io/badge/docs-hexdocs.pm-blue)](https://hexdocs.pm/facade)

Facade is a tiny macro for defining:

- a behaviour callback (`@callback`)
- a delegating "facade" function (`def`)

from a single `@spec`-shaped declaration.

## Why this exists

`Facade` is for codebases that want *both*:

- a behaviour contract (`@callback`) for implementations
- a small, typed wrapper API that delegates to an implementation module

and want those two to be defined once (so they don’t drift).

The key design choice is that the implementation module is passed explicitly at
the call site as the first argument:

```elixir
MyApp.MyBehaviour.foo(MyApp.BehaviourImpl, arg1, arg2)
```

### When you should use it

- The implementation really is dynamic (chosen at runtime, per call).
- You want dependency injection without global config (tests can pass a fake
  module directly).
- You keep re-reading behaviour specs and want an ergonomic, discoverable API
  (`MyApp.Port.fun/...`) that still enforces `@behaviour` on implementations.

### When you should not use it

- There is only one implementation, or it’s selected once at startup; prefer a
  normal module and plain function calls.
- You want the "traditional" Elixir approach of selecting implementations via
  application env/config at a single boundary (e.g. `Application.get_env/3`).
  `Facade` intentionally diverges by making the dependency explicit per call.

### What it is not

- Not a runtime dispatch framework: it only generates `def foo(mod, ...)`,
  which calls `mod.foo(...)`.
- Not a container/service locator: it does not store or resolve implementations.
- Not a replacement for behaviours: the behaviour is still the contract; `defapi/1`
  just generates the wrapper and the callback together.

## Installation

Add `facade` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:facade, "~> 0.1.0"}
  ]
end
```

## Quick start

Define a "port" module (behaviour + facade functions):

```elixir
defmodule MyApp.Clock do
  use Facade # or `import Facade` if you don't want `validate/1` or `validate!/1`

  @doc "Returns the current unix timestamp."
  defapi now() :: integer()
end
```

Provide an implementation:

```elixir
defmodule MyApp.SystemClock do
  @behaviour MyApp.Clock

  @impl MyApp.Clock
  def now, do: System.os_time(:second)
end
```

Call the facade by passing the implementation module as first argument:

```elixir
MyApp.Clock.now(MyApp.SystemClock)
```

Optionally validate an implementation module at runtime:

```elixir
MyApp.Clock.validate!(MyApp.SystemClock)
```

## What `defapi/1` generates

Given:

```elixir
defapi foo(a :: integer(), l :: list(x)) :: {integer(), list()} when x: integer()
```

`Facade` will generate roughly:

```elixir
@spec foo(mod :: module(), a :: integer(), l :: list(x)) :: {integer(), list()} when x: integer()
def foo(mod, a, l), do: mod.foo(a, l)

@callback foo(a :: integer(), l :: list(x)) :: {integer(), list()} when x: integer()
```

The spec you pass to `defapi/1` follows the same rules as `@spec`:

- zero-arity can be written as `foo :: atom()` or `foo() :: atom()`
- guards are supported via `when`
- attributes like `@doc`, `@doc false`, and `@deprecated` on the call site are
  copied onto the generated function

## Use cases

- Adapters/ports: define a stable boundary and multiple implementations.
- Testing: pass a fake module instead of patching globals.
- Plug-in style systems: accept an implementation module as an argument.

## Testing example

Because the implementation is an explicit argument, tests can pass a small fake
module:

```elixir
defmodule MyApp.FakeClock do
  @behaviour MyApp.Clock
  @impl MyApp.Clock
  def now, do: 1_700_000_000
end

assert MyApp.Clock.now(MyApp.FakeClock) == 1_700_000_000
```

## Runtime validation

When you `use Facade` in a behaviour module, `Facade` also defines:

- `validate/1` — returns `:ok | {:error, missing_callbacks}`
- `validate!/1` — raises `Facade.MissingCallbacksError` when required callbacks are missing

Optional callbacks declared via `@optional_callbacks` are ignored by validation.

## Notes and caveats

- `defapi/1` is intended to be used in the module that defines the behaviour.
  Implementation modules should `@behaviour ThatModule`.
- The generated facade function simply calls `mod.fun(args...)`. It does not
  perform runtime checks that the module implements the behaviour.

## Generating docs

This project uses ExDoc. Generate docs locally with:

```bash
mix deps.get
mix docs
```

## License

MIT. See `LICENSE`.