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

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
wmnnd
Hey folks, I wanted to share with you a project I’ve been working on for a while: It’s an Open Source (AGPLv3) newsletter tool. Think Ma...
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
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
sntran
In my third year of college, around 2010, I found Erlang. A few years later, I found Elixir. I liked the ideas at once: many small proces...
New
Ganna
Hi everyone, I have 8 years of production experience with Erlang/OTP and I’m just starting to get to know Elixir. What would you learn f...
New
gtcode
I’ve been tinkering with Lynx (by José) via hold_ledger. I used AI to write the code, a separate specification, the tests, and the Lean p...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews