Dependency injection in Julia


#1

Wondering if anybody found a good way to implement dependency injection in Julia. I have many situations where I use “adapters”: take for instance a Database module which uses various adapters (MySQL, SQLite, Postgres, etc).

And what I usually end up with is:

  • a const in the module global scope which references the adapter
  • a lot of eval statements in the line of:
if haskey(SearchLight.config.db_config_settings, "adapter") && SearchLight.config.db_config_settings["adapter"] != nothing
  db_adapter = Symbol(SearchLight.config.db_config_settings["adapter"] * "DatabaseAdapter")

  include("database_adapters/$(db_adapter).jl")
  Core.eval(@__MODULE__, :(using .$db_adapter))
  Core.eval(@__MODULE__, :(const DatabaseAdapter = $db_adapter))
  Core.eval(@__MODULE__, :(export DatabaseAdapter))
else
  const DatabaseAdapter = Nothing
end

Looks like a code smell to me.

I think a nice solution would be to have a mechanism to pass arguments when using a module, in the line of:
using Database(adapter = MySQLAdapter)


#2

I think a more Julian approach would be to utilize a kind of DataBaseAdapter interface and rely on the caller to call setup appropriately; something like:

module Adapters

abstract type Adapter end

"Subtype must define `connect!(x::MyAdapter)` to satisfy interface"
function connect! end

"Subtype must define `query(x::MyAdapter)` to satisfy interface"
function query end

end



module AdapterAs

import Adapters

struct AdapterA
end

Adapters.connect!(::AdapterA) = # ...
Adapters.query(::AdapterA) = # ...

end



module App

import Adapters

const ADAPTER = Ref{Adapter}()

function setadapter!(x::Adapter)
    ADAPTER[] = x
end

function query(sql::String)
    Adapters.connect!(ADAPTER[])
    Adapters.query(ADAPTER[])
end

end

With this kind of setup, you leave it to the driving application or script to call import AdapterAs and App.setadapter!, or potentially import AdapterBs if there was an alternative adapter they wished to use.


#3
  1. Naming is a bit hacky. I would try to slim it.
  2. I would try to factorize by parametrizing as mush as i can (see below)
  3. otherwise not a bad pattern
# parametrizable with (conf section=="DB")

if haskey(FOO.config.db, "driver") && FOO.config.db["driver"] != nothing        # todo parametrize config section "db"
    driver = Symbol(FOO.config.db["driver"] * "DbDriver")                       # parametrize julia type suffix

    include("db/$(driver).jl")                                                 # parametrize directory
    Core.eval(@__MODULE__, :(using .$driver))
    Core.eval(@__MODULE__, :(const DbDriver = $driver))                         # parametrize julia type suffix (see previous one)
    Core.eval(@__MODULE__, :(export DbDriver))                                  # idem
else
    const DbDriver = Nothing                                                    # idem
end


#4

Another reason for reviving DBAPI? :wink: