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
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.
I’m actually happy with the naming. Must not forget that this is not user code, but framework code. It’s not meant to be typed by the users of the library (they just need to define the config Dict). In such cases (write once, read multiple times) I prefer to err on the side of readability.
Wouldn’t that just move the problem somewhere else?
Also, curious if there’s any solution for
defining optional dependencies (right now I have to make SQLite.jl, MySQL.jl and PgLib.jl explicit dependencies of my package forcing users to install all, even if they’ll only use one backend).
creating a plugins system where a 3rd party user can extend my library and add their own dependencies (like in this case, somebody plugging in a MariaDB adapter with its dependency on an a MariaDB.jl)
Naming can be very subjective, so i will not try to defend one vs another. My purpose was to reduce variations without real functionality eg. noise to better allow factorization of function. If you feel it works well. That’s ok.
My understanding is that it is not only a problem of user vs admin. But a problem of toolchain selection.
A choice not only made by one person and forever. It can be very wide and open.
There are only two hard things in Computer Science: cache invalidation and naming things.
– Phil Karlton
What I meant was that the above code was “private”. The user would just say driver = MySQL.
Yes, the Plots.jl backends analogy is exactly what I have in mind. I don’t want to comment yet as I still have to test Requires.jl (although deep in my heart I have the feeling that Pkg will complain if the dependencies are not explicitly declared and installed – and that users won’t be able to add new “backends” on the fly without changing the Project.toml of the original package).