binaryseed

binaryseed

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

First Post!

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.

Most Liked

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 )

Where Next?

Popular in Discussions Top

AstonJ
If a newbie asked you about Phoenix Contexts, how would you explain the basics to them? Feel free to be as concise or in-depth as you li...
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19814 150
New
rower687
Hi all, I’ve been reading a lot about the “let it crash” term and how supervising processes and the whole messaging passing make an elixi...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
slashdotdash
Phoenix Live View is now publicly available on GitHub. Here’s Chris McCord’s tweet announcing making it public.
New

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement