Using dry-configuration as a IoC container


I’m developing a small gem, while at the same time I’m learning and trying to apply concepts used in dry-rb :slight_smile:

In this gem I’m using dry-configurable to provide the user with a way to configure some settings, some of them being expected to be constants implementing certain interfaces which the end user has to write.

Used in this way, as I see it, dry-configurable acts as a kind of IoC: it allows higher level classes to depend on abstractions instead of on concrete classes.

This was about the interfaces the user is forced to write. Now, some high level classes in my gem code also have some collaborators and I was thinking in a way to be able to inject them. First I thought about using a container with dry-container, but then I realized that the main reason I want to be able to inject my collaborators is to provide the user with a way to overwrite some behavior at the same time that I keep my code simple. So, being user configurable things, I think that the best place where that collaborators can be is as dry-configurable settings. This enforces the view of dry-configuration as an IoC container.

My question is just about how you see it. I’m quite newbie with this concepts and I wouldn’t like to fell in some anti-pattern or to do things more complicated that they are.

Thank you!

The container gem is meant to be used as a registry of objects for injection, and you can provide a default container, and a user container, and then just merge them to get the final run-time container. ie:

irb(main):006:0> c1 =
=> #<MyContainer:0x007fd76f932b20 @_container={}>
irb(main):007:0> c2 =
=> #<MyContainer:0x007fd76f928418 @_container={}>
irb(main):008:0> c1.register(:foo, 'foo')
=> #<MyContainer:0x007fd76f932b20 @_container={"foo"=>#<Dry::Container::Item:0x007fd76f9103e0 @item="foo", @options={:call=>false}>}>
irb(main):009:0> c2.register(:foo, 'my foo')
=> #<MyContainer:0x007fd76f928418 @_container={"foo"=>#<Dry::Container::Item:0x007fd770859428 @item="my foo", @options={:call=>false}>}>
irb(main):010:0> c1.merge(c2)[:foo]
=> "my foo"

Thanks for your response @solnic.

Well, what I actually had in mind was using dry-configurable with dry-auto_inject in the following way (now that dry-configurable can be accessed through a Hash interface it is possible):

# My Gem code

module MyGem
  extend Dry::Configurable

  setting :primitive, 'I am a string'
  setting :expected_interface

  Import = Dry::AutoInject(config)

module MyGem
  class DoSomething
    include Import["primitive"]

    # ...

  class DoSomethingElse
    include Import["expected_interface"]

    def call(arg)
      # ..

# User code
MyGem.configure do |config|
  setting :expected_interface, UserClass

class UserClass
  def self.expected_method(arg)
    # ...

And I have some thoughts about whether I’m using or abusing all of that. In particular:

1 - About using dry-configurable as an IoC container. I think it has some similarities: a kind-of global container where dependencies/things can be registered. The main difference being a dry-container container is best suited for registering own code while dry-configurable is best suited for users to register things to be used by a library.

2 - Accepting 1, whether it is good to use an IoC container to register simple primitives, like primitive string setting in the example.

3 - Accepting 1 but rejecting 2, whether then I should provide my gem users with two interfaces to configure things: something like dry-configurable for primitives and something like dry-container for expected interfaces.

4 - And then about my gem internal collaborators. Accepting 1, whether if I could register them also in dry-configurable container, so, even if not generally necessary, users would have a simple way to extend or modify my gem behaviour; for example, exchanging one collaborator for another one.

I understand these questions are very open, maybe opinionated and near ontological :smiley: But at least I would like to be sure not being about to do some aberration :smiley:

dry-auto_inject can be used with anything that implements #[] so if you find dry-configurable to be better for your gem then go ahead and use it :slight_smile:

Ok, thank you very much @solnic :slight_smile: