Skip to content

Instantly share code, notes, and snippets.

@neenjaw
Forked from timruffles/dyanmic_or_di_elixir.md
Created June 11, 2020 04:23

Revisions

  1. @timruffles timruffles revised this gist Jun 10, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ Here are the approaches I can see. The two points that seem to divide the approa



    ## Behaviours + passed
    ## Passing modules

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

  2. @timruffles timruffles revised this gist Jun 10, 2014. 1 changed file with 57 additions and 19 deletions.
    76 changes: 57 additions & 19 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -2,24 +2,18 @@ In many production systems you'll want to have one module capable of talking to

    Here are the approaches I can see. The two points that seem to divide the approaches are their tool-ability (dialyzer) and their ability to handle stateful implementations (which need a `pid`).

    ```exlixir
    defmodule UsesCache do
    def start(cache,cache_pid) do
    cache.put(cache_pid,:hello)
    true = cache.cached?(cache_pid,:hello)
    end
    end
    ```


    ## Behaviours

    ## Behaviours + passed

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    ```elixir
    defmodule Cache do
    use Behaviour
    defcallback cached?(any,any) :: boolean
    defcallback put(any,any) :: nil
    end

    defmodule Cache.Memory do
    @@ -28,12 +22,24 @@ defmodule Cache.Memory do
    end

    defmodule Cache.Redis do
    def put(redis_pid,x) do {:ok,1} = Redis.set x, 1
    def put(redis_pid,x) do
    {:ok,1} = Redis.set redis_pid, x, 1
    end
    def cached?(redis_pid,x) do
    {:ok,x} = Redis.get(redis_pid,x)
    x != nil
    end
    end

    # usage
    defmodule UsesCache do
    def start(cache,cache_pid) do
    cache.put(cache_pid,:hello)
    true = cache.cached?(cache_pid,:hello)
    end
    end

    UsesCache.start(Cache.Memory,HashSet.new)
    ```

    Similar idea to duck-typing.
    @@ -51,23 +57,43 @@ Write a `Protocol` for the functionality. You can then pass in an opaque value t
    - requires stub implementations for testing

    ```elixir
    defmodule Cache do
    use Behaviour
    defcallback cached?(any,any) :: boolean
    defprotocol Cache do
    def cached?(id,item)
    def put(id,item)
    end

    defmodule Cache.Memory do
    def put(set,x) do: Set.add set, x
    def cached?(set,x) do: Set.member? map, x
    defstruct set: nil
    alias __MODULE__, as: Mod
    defimpl Cache, for: Mod do
    def put(%Mod{set: set},x) do: Set.add set, x
    def cached?(%Mod{set: set},x) do: Set.member? map, x
    end
    end

    defmodule Cache.Redis do
    def put(redis_pid,x) do {:ok,1} = Redis.set x, 1
    def cached?(redis_pid,x) do
    {:ok,x} = Redis.get(redis_pid,x)
    x != nil
    defstruct redis: nil
    alias __MODULE__, as: Mod
    defimpl Cache, for: Mod do
    def put(%Mod{redis: redis},x) do
    {:ok,1} = Redis.set redis, x, 1
    end
    def cached?(%Mod{redis: redis},x) do
    {:ok,x} = Redis.get(redis,x)
    x != nil
    end
    end
    end

    # usage
    defmodule UsesCache do
    def start(cache) do
    Cache.put(cache,:hello)
    true = Cache.cached?(cache,:hello)
    end
    end

    UsesCache.start(%CacheMemory{set:HashSet.new})
    ```

    ## Passing callbacks
    @@ -86,6 +112,18 @@ end

    Now the `callback` field of state can be used by functions of this `gen_server` module.

    ```elixir
    # usage
    defmodule UsesCache do
    def start(put,cached) do
    put.(:hello)
    true = cached.(:hello)
    end
    end

    # can create callbacks from anything: stateful, stateless etc
    ```

    + works with any implementation, even ad-hoc
    + dialyze-able
    - pass many values for large APIs
  3. @timruffles timruffles revised this gist Jun 10, 2014. 1 changed file with 80 additions and 7 deletions.
    87 changes: 80 additions & 7 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,74 @@
    Some notes on the related goals of DI/dynamic dispatch. In many production systems you'll want to have one module capable of talking to many potential implementations of collaborator modules (e.g a in memory cache, a redis-based cache etc). While testing it's useful to control which module the module under test is talking to.
    In many production systems you'll want to have one module capable of talking to many potential implementations of a collaborator module (e.g a in memory cache, a redis-based cache etc). While testing it's useful to control which module the module under test is talking to.

    I'm still pondering the pros/cons of the approaches; my least favourite is the last as it feels icky to rely on state.
    Here are the approaches I can see. The two points that seem to divide the approaches are their tool-ability (dialyzer) and their ability to handle stateful implementations (which need a `pid`).

    ## Passing modules
    ```exlixir
    defmodule UsesCache do
    def start(cache,cache_pid) do
    cache.put(cache_pid,:hello)
    true = cache.cached?(cache_pid,:hello)
    end
    end
    ```


    ## Behaviours

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    Similar idea to duck-typing. A question arises when you have a module with state - do you pass in a `pid` too? If that's the case, perhaps encapsulating it with a `Protocol` is a better angle, as it'd allow non-stateful implementations to pass something else in.
    ```elixir
    defmodule Cache do
    use Behaviour
    defcallback cached?(any,any) :: boolean
    end

    defmodule Cache.Memory do
    def put(set,x) do: Set.add set, x
    def cached?(set,x) do: Set.member? map, x
    end

    defmodule Cache.Redis do
    def put(redis_pid,x) do {:ok,1} = Redis.set x, 1
    def cached?(redis_pid,x) do
    {:ok,x} = Redis.get(redis_pid,x)
    x != nil
    end
    end
    ```

    Similar idea to duck-typing.

    + simple
    - dializer(?)
    - modules with state - you'd have to pass a `pid` too, e.g `{module,pid}` (eugh)

    ## Protocols + values

    Write a `Protocol` for the functionality. You can then pass in an opaque value to collaborators, and the implementation will be decided at runtime.

    + handles stateful and stateless implementations easily
    + dialyze-able
    - requires stub implementations for testing

    ```elixir
    defmodule Cache do
    use Behaviour
    defcallback cached?(any,any) :: boolean
    end

    defmodule Cache.Memory do
    def put(set,x) do: Set.add set, x
    def cached?(set,x) do: Set.member? map, x
    end

    defmodule Cache.Redis do
    def put(redis_pid,x) do {:ok,1} = Redis.set x, 1
    def cached?(redis_pid,x) do
    {:ok,x} = Redis.get(redis_pid,x)
    x != nil
    end
    end
    ```

    ## Passing callbacks

    @@ -24,10 +86,21 @@ end

    Now the `callback` field of state can be used by functions of this `gen_server` module.

    + works with any implementation, even ad-hoc
    + dialyze-able
    - pass many values for large APIs

    ## Stateful

    Create a stateful module that holds the module, [refer to that](https://github.com/ericmj/hex_web/blob/84516f0320aa4c4840054831666a3f5d5958a9d6/lib/hex_web/router.ex#L29).

    - doesn't work for use-cases with many instances (pass-the-pid)
    + dialyzer - need a Behaviour for the return type of the getter

    ## Module generation

    You could [generate](https://gist.github.com/alco/c08256ae72d31c76e767) a module based on a run-time config.

    ## Stateful

    Create a stateful module that holds the module, [refer to that](https://github.com/ericmj/hex_web/blob/84516f0320aa4c4840054831666a3f5d5958a9d6/lib/hex_web/router.ex#L29).
    - moving parts
    - doesn't work for use-cases with many instances (pass-the-pid)
    - dialyzer - seems likely to throw it off
  4. @timruffles timruffles revised this gist Jun 10, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    Some notes on the related goals of DI/dynamic dispatch. While testing it's useful to control which module the module under test is talking to. In many production systems you'll want to have one module capable of talking to many potential implementations of collaborator modules.
    Some notes on the related goals of DI/dynamic dispatch. In many production systems you'll want to have one module capable of talking to many potential implementations of collaborator modules (e.g a in memory cache, a redis-based cache etc). While testing it's useful to control which module the module under test is talking to.

    I'm still pondering the pros/cons of the approaches; my least favourite is the last as it feels icky to rely on state.

  5. @timruffles timruffles revised this gist May 20, 2014. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    Some notes on the related goals of DI/dynamic dispatch. While testing it's useful to control which module the module under test is talking to. In many production systems you'll want to have one module capable of talking to many potential implementations of collaborator modules.

    I'm still pondering the pros/cons of the approaches; my least favourite is the last as it feels icky to rely on state.

    ## Passing modules

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).
  6. @timruffles timruffles revised this gist May 20, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ Some notes on the related goals of DI/dynamic dispatch. While testing it's usefu

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    Similar idea to duck-typing.
    Similar idea to duck-typing. A question arises when you have a module with state - do you pass in a `pid` too? If that's the case, perhaps encapsulating it with a `Protocol` is a better angle, as it'd allow non-stateful implementations to pass something else in.

    ## Passing callbacks

  7. @timruffles timruffles revised this gist May 20, 2014. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,8 @@ Some notes on the related goals of DI/dynamic dispatch. While testing it's usefu

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    Similar idea to duck-typing.

    ## Passing callbacks

    For a single method, you could just pass a function. Then in tests you pass a stub method, and in production you can wrap up the real module behind it.
  8. @timruffles timruffles revised this gist May 20, 2014. 1 changed file with 7 additions and 4 deletions.
    11 changes: 7 additions & 4 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,10 @@
    # Passing modules
    Some notes on the related goals of DI/dynamic dispatch. While testing it's useful to control which module the module under test is talking to. In many production systems you'll want to have one module capable of talking to many potential implementations of collaborator modules.

    ## Passing modules

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    # Passing callbacks
    ## Passing callbacks

    For a single method, you could just pass a function. Then in tests you pass a stub method, and in production you can wrap up the real module behind it.

    @@ -17,10 +19,11 @@ end
    ```

    Now the `callback` field of state can be used by functions of this `gen_server` module.
    # Module generation

    ## Module generation

    You could [generate](https://gist.github.com/alco/c08256ae72d31c76e767) a module based on a run-time config.

    # Stateful
    ## Stateful

    Create a stateful module that holds the module, [refer to that](https://github.com/ericmj/hex_web/blob/84516f0320aa4c4840054831666a3f5d5958a9d6/lib/hex_web/router.ex#L29).
  9. @timruffles timruffles revised this gist May 20, 2014. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions dyanmic_or_di_elixir.md
    Original file line number Diff line number Diff line change
    @@ -6,13 +6,15 @@ Modules are first class, so you can pass them in. Used in [EEx](https://github.c

    For a single method, you could just pass a function. Then in tests you pass a stub method, and in production you can wrap up the real module behind it.

    def start_link({some_callback}) do
    :gen_server.start_link(@name,SomeModule,{some_callback},[])
    end
    ```elixir
    def start_link({some_callback}) do
    :gen_server.start_link(@name,SomeModule,{some_callback},[])
    end

    def init({some_callback}) do
    {:ok,%State{callback: some_callback})
    end
    def init({some_callback}) do
    {:ok,%State{callback: some_callback})
    end
    ```

    Now the `callback` field of state can be used by functions of this `gen_server` module.
    # Module generation
  10. @timruffles timruffles renamed this gist May 20, 2014. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  11. @timruffles timruffles created this gist May 20, 2014.
    24 changes: 24 additions & 0 deletions dyanmic_or_di_elixir.ex
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    # Passing modules

    Modules are first class, so you can pass them in. Used in [EEx](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/compiler.ex#L16), where passed module must implement a [behaviour](https://github.com/elixir-lang/elixir/blob/master/lib/eex/lib/eex/engine.ex#L27-L31).

    # Passing callbacks

    For a single method, you could just pass a function. Then in tests you pass a stub method, and in production you can wrap up the real module behind it.

    def start_link({some_callback}) do
    :gen_server.start_link(@name,SomeModule,{some_callback},[])
    end

    def init({some_callback}) do
    {:ok,%State{callback: some_callback})
    end

    Now the `callback` field of state can be used by functions of this `gen_server` module.
    # Module generation

    You could [generate](https://gist.github.com/alco/c08256ae72d31c76e767) a module based on a run-time config.

    # Stateful

    Create a stateful module that holds the module, [refer to that](https://github.com/ericmj/hex_web/blob/84516f0320aa4c4840054831666a3f5d5958a9d6/lib/hex_web/router.ex#L29).