Advice how to go about library configuration from Elixir 1.9 and on?

I feel like options aren’t bad at all. I’m using ex_cldr and ecto in their non dynamic functions compiled into my module modes and I’m quite happy to use those as e.g. MyRepo.func and not Ecto.Repo.func(MyRepo, …), but I can also see the other side of the coin where this is limiting (e.g. commanded is a lib, which only supports a single “instance” at the moment).

I’d at least expect that users might already have some infrastructure in their applications to handle the many databases in their business context. Mustn’t be, but it’s nice to plug things into the places they belong instead of having similar setups in many places. For the rest you could still provide some genservers if you really want.

1 Like

That’s a pretty useful plug though! It’s kind of funny that after I reviewed the file I found myself agreeing with everything in it and I had like 95% the same ideas. I take it as a compliment. :003: Thank you, this has been both enlightening and reassuring at the same time.

You are far ahead of me. Pooling I’ll delegate to db_connection – which I need to do when I get to the Ecto 3.1+ adapter anyway so, you know, double win. I’ll prefer configuration that’s local for each sqlite DB handle.

Like @LostKobrakai and @wojtekmach, I believe OTP wiring should not be mandatory unless it is absolutely technically unavoidable. To me it’s just a way to have the users of the library follow a 2-3 step tutorial on the library’s GitHub README page and have all their databases automatically managed by OTP while they only call an API that takes an atom name instead of an sqlite handle as a first argument. They can just as easily never register the GenServer wrapper I’ll provide in the library under their app’s Supervisor and use their own way to manage multiple sqlite handles, like a process dict, application configuration, ETS, or whatever else.

Speaking of that though, I figure (both by myself and reading yours and the other guys’ comments) that having a pure FP structure one can pass around should be the default and the bare minimum a library provides. Having any kind of OTP wiring should just be a convenience in terms of “how do I have something else manage all my NIF handles and I just call this API with names of the handles I choose and not care how it all happens under the hood”.

So if you don’t have a pure FP data structure that can be passed around then I’d say go for that and make processes optional.

Same as my reasoning. Coming from 15 years of imperative and OOP languages before Elixir, I am avoiding surprise mechanics like the plague. Thanks for validation! :041:

1 Like

Not sure mine and your point of view contradict each other. To me making a thin GenServer wrapper that the user of the library can plug into their app’s Supervisor is exactly to “plug things into the places they belong” (with the caveat that the user does not want to only pass a self-contained state structure around; if they are not satisfied by that then IMO a thin OTP wiring is quite a good way to go beyond the pure FP approach).

Unless I am reading you wrong?

I realise that my library, when ready, can be criticised that it comprises of 3 parts in one instead of having 1 base library and 2 plugins for it (baseline: sqlite client, plugins: OTP wiring and Ecto 3.1+ adapter) but it’s my personal programmer philosophy that having 30KB extra .beam files is worth the reduced complexity, as opposed to every user of the library having to manually wire the core library with its plugins – which might turn out to be an error-prone process and ruin the experience.

So I prefer to give you a library with some batteries included, provided the batteries are strictly opt-in and aren’t forcing you to deal with them even when you don’t need them.

If the batteries grow too big though then I’ll reconsider breaking the library down to smaller components. I believe it’s too early to over-optimise like this just yet though.

1 Like

What I try to do is:

  • If a standalone application, then put the configs in the application environment along with a function to call to have it perform things that it needs notifications about (like pool size), with override on individual function calls when appropriate.

  • If a set of GenServer’s then default application environment with override of options passed to start_link with override on individual function calls when appropriate, with a needed function call to have it be notified of config changes on things like pool size or whatever.

  • If loose modules then default application environment with override of options on individual function calls when appropriate.

In addition, if defaults don’t exist in the application environment then it should call a ‘provider’ (whatever that is, database, system environment, a remote server, whatever) to populate the application environment values.

I want a library to make all this pattern work, yes it will require a lot of macro magic to not balloon to huge amounts of user code, but still, it should be simple to use, inline, no annoying large schema definitions just for function options, etc…

1 Like

Sounds like yet another library you aren’t going to write. :stuck_out_tongue:

But otherwise, I partially agree. The part I wouldn’t do is to invest a lot in config providers – at least for now. Another thing I wouldn’t do is application environment – it’s unsuitable for libraries.

The priority order is going to be a self-sufficient structure containing both DB handle and configuration and if you find that annoying, you’ll be able to pass that configuration via start_link in a thin GenServer wrapper and not think about configurations and DB handles.

Oh how I wish I had infinite free time… ^.^;

But yes, that is why I described it, so maybe someone else will make it. :wink:

That would be nothing but a simple interface/behaviour, I wouldn’t expect anything more complicated then reading from the system environment being included with it, most of the times these are very platform-dependent.

Libraries should still always register an application, it makes handling updates far cleaner, and by registering an application they still get an application environment, even if the application is otherwise empty.

But for example, I wrote my library in this way.

1 Like

Oh mine does but doesn’t use Mix.Config or Elixir’s 1.9 Config at all, nothing else.

1 Like

It doesn’t need to, just when you need to use a configuration thing just do Application.get_env/3. This is all Erlang stuff, not even Elixir. :slight_smile:

Nope, still don’t want to! I am making a DB access library after all; everything in it should be configurable per connection / NIF handle. There’s simply no place for any global configuration there.

1 Like

Only defaults, unless you want to force the user to pass in every configuration when needed, which is of course possible too if a bit wordy at times (though explicit is clear!). ^.^

1 Like

They will be there of course. The library user does not even need to supply a config, everything comes with sensible defaults – including the DB which is defaulting to an unnamed in-memory DB.

1 Like

Sure but once you go down this path people will start asking for a way to load configuration from Redis, ActiveDirectory, LevelDB and before you know it, I’ll have to invoke AWS Lambdas to get somebody’s sqlite configuration… Nope! :009:

1 Like

That’s all on them to make adapters for whatever their application needs, if they want to distribute it as a library then they can, but otherwise… :wink: