What is the best way to initialize dependencies?

Like @solnic mentioned, I’d definitely love to see how you could go using providers here.

One thing that might not be clear about providers (I’ve just checked our relevant guide and we could definitely do better explaining this) is that they can register zero or more components, emphasis on the zero.

So many of the initializers you’re sharing here could be providers that just do not register any components, and instead do the kinds of global gem configuration that your examples show.

A helpful lens to apply here is: what happens if I only want to partially boot my app, i.e. boot it without certain slices, or prepare the app and then load specific components only? In your initializers arrangement, even in these cases you’d be loading every single initializer, which in many cases would mean that your app is loading more code and doing more setup work than necessary.

An example here might be if you have part of your app that is not web-facing that you want to boot independently. In that case, it makes no sense to require and configure rack/attack, so you’d be better off just not loading it at all.

The related advantage of providers is that they can be loaded in different ways that mesh well with Hanami’s booting flexibility. For example:

  • For a fully booted Hanami app (when requiring "hanami/boot" or running Hanami.boot), every provider will be loaded, which is on par with your provided initializers arrangement
  • They can be started explicitly, e.g. Hanami.app.start :my_provider
  • They can also be started implicitly, when a component is resolved with a base key segment that matches a provider name, e.g. when a "foo.bar" component is resolved, Hanami will look for a :foo provider and start it if found
  • They can be put into separate slices as needed (e.g. slices/foo/config/providers/my_provider.rb); this could of course be manually replicated with a collection of initializers like you shared, but that’s still extra work

I suspect that at least some of your initializers could be ported to providers to take advantage of these benefits.

Another way to do certain kinds of configuration could be to use parent classes. This might suit your question in How should Dry Schema and Validations be prepared with monads? - #2 by bkuhlmann (which I’ve had on my list to answer since you posted, sorry for the delay), and the related dry.rb initializer example from your post here.

What it would look like is having a parent e.g. MyApp::Validation::Contract class that all your validation contracts inherit from. It’d look something like this (at app/validation/contract.rb):

# auto_register: false

require "dry/validation"

Dry::Schema.load_extensions :monads
Dry::Validation.load_extensions :monads

module MyApp
  module Validation
    class Contract < Dry::Validation::Contract
    end
  end
end

Then in any place where you create your own validation contracts, instead of inheriting from Dry::Validation::Contract directly, instead inherit from MyApp::Validation::Contract. This will ensure your base contract class is autoloaded, and with it, the global configuration for the validation gems that it uses.

The general question I ask when I try to organise Hanami configuration is this: given a prepared app only (Hanami.prepare), when I resolve a single component, can everything be configured sufficiently for that single component to work, and nothing more?

I then try and arrange things to create a “yes” answer to that question :slight_smile:

I’m really glad you’re asking these things now, @bkuhlmann, because I’m sure there are aspects of the framework that could be improved to better handle the needs from a wider range of users (and use cases) out in the real world!

I’d definitely be keen to hear your thoughts on everything above.