binaryseed

binaryseed

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

Showing Posts 1 to 2

jeffs

jeffs

Thanks @binaryseed, I think this is great! I am following Jose’s post on mocks, but I struggle with swapping out contract implementations in a frictionless way (dependency injection sadly doesn’t work that well in my case). I recently discovered the defdelegate + @behaviours pattern, but in my particular case I need runtime config, so I was stuck. I believe this will solve my needs perfectly, so thanks for sharing!

I’m not sure if there are any gotchas or anti-patterns around this, so I am curious to hear what others think as well, and if I run into any trouble with it I will share.

jeffs

jeffs

I did notice one potential issue - Kernel.Utils.defdelegate says it will be removed in v2.0
https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel/utils.ex#L28

(Oh, and I only just now realized this is 2 years old. That might explain why I’m getting an error using it at the moment. The error is fixable if you remove the wrapping square brackets from the match of Kernel.Utils.defdelegate )

— All posts loaded —

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews