I am building an http client library (see here for source) that wraps some of the well known libraries (such as HTTPoison) to provide the ability to build composable http requests. I originally built this with a hard dependency on HTTPoison and I have now split this out into an adapter based architecture, however I am unsure on my approach so far.
Currently my adapter doesn’t have to depend on the generic library as it only has to implement one public function that takes a single parameter. I’m unsure if this is my OO ways kicking in, but I feel as though I should have a strongly defined interface that adapters are forced to implement, maybe a protocol or a simple base module that the adapter has to
Anybody have any thoughts?
I don’t have experience doing this, but my take would be to use behaviours to enforce a specific interface. If you have the time, you could always take a look at Ecto (adapters). @michalmuskala once wrote how to implement a new Ecto adapter, it sounded quite good: http://michal.muskala.eu/2015/07/07/creating-ecto-adapters.html
Take a look at a package that I put together called Hora. It’s a simple adapter based password hashing library.
You can see how I defined the interface here: https://github.com/sticksnleaves/hora/blob/master/lib/hora/adapter.ex
And one of the implementations here: https://github.com/sticksnleaves/hora/blob/master/lib/hora/adapters/bcrypt.ex
Like @Tuxified said you probably want to look at Elixir’s
behaviours for defining the interface.
Many thanks to you both, a behaviour is exactly what I need.
Hmm… so it’s going to be a library, that calls an adapter, that wraps httpoison, that wraps hackney. I’ve had trouble with setups like this before because it’s difficult to debug, and there’s a lag between new features appearing in the base library and getting support for them in the layers of wrappers. YMMV though.
A similar example is bamboo, which has a bamboo_smtp adapter, which wraps gen_smtp. Sadly a lot of gen_smtp features are not exposed through the adapter. It’s a good example though if you’re interested in behaviors and adapters, its purpose is also very similar (make email construction composable).
Yes, sadly a wrapper of a wrapper, etc…
But this is what prompted me to move to an adapter setup as I intend on implementing a hackney adapter which will at least remove one level of wrapping.
I have also felt this pain more than once, usually when trying to use a newer library in a larger framework such as phoenix for example. I guess there is a line to draw before each library just re-implements the functionality of the others it would otherwise depend upon.