Dynamic defdelegate - idea for contract shim modules

I like using defdelegate along with @behaviours to make nice simple contract shim modules. The only gotcha is that defdelegate is a compile-time construct, so the module that is delegated to is fixed per-env upon compile.

I’ve put together a little experiment that mirrors defdelegate but enables runtime configuration of the delegated module..

It lets you define a contract module

defmodule SomeContract do
  use RuntimeDelegate, default: DefaultImpl

  @callback fun(binary, atom) :: term
  defdelegate fun(foo, bar)
end

defmodule DefaultImpl do
  @behaviour SomeContract

  def fun(foo, bar) do
    # Do the real thing
    {__MODULE__, foo, bar}
  end
end

defmodule AlternateImpl do
  @behaviour SomeContract

  def fun(foo, bar) do
    # Do it another way
    {__MODULE__, foo, bar}
  end
end

And then you can control it at runtime:

assert {DefaultImpl, "foo", :bar} = SomeContract.fun("foo", :bar)

Application.put_env(:contract, SomeContract, AlternateImpl)
assert {AlternateImpl, "foo", :bar} = SomeContract.fun("foo", :bar)

It could be used to avoid hitting APIs in tests, or to do controlled rollout of new code, etc…

It’s not a lot of code, just a little macro that re-uses some of the code for Kernel.defdelegate

defmodule RuntimeDelegate do
  defmodule DefDelegate do
    defmacro defdelegate(fun) do
      fun = Macro.escape(fun, unquote: true)
      quote bind_quoted: [fun: fun] do
        [{name, args, as, as_args}] = Kernel.Utils.defdelegate(fun, [])
        def unquote(name)(unquote_splicing(args)) do
          impl = Application.get_env(:contract, __MODULE__) || @default_impl
          unless impl, do: raise(ArgumentError, "No implementation found")
          impl.unquote(as)(unquote_splicing(as_args))
        end
      end
    end
  end

  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      @default_impl opts[:default]
      import Kernel, except: [defdelegate: 2]
      import RuntimeDelegate.DefDelegate
    end
  end
end

Thought I’d toss it out there to see what folks thought.. I think of it in the spirit of Jose’s article about explicit contracts: Mocks and explicit contracts « Plataformatec Blog

5 Likes