Hi kofno,
Thank you for taking an interest in dry-rb.
I think your point regarding “containers everywhere” applies specifically to dry-container. I created dry-container in an attempt to make dependency injection easier to use in Ruby. From what I’ve seen in Ruby, people seem to promote good practices such as code to an interface (in the form of duck-typing), favour composition over inheritance etc. But judging by the Ruby code that I’ve seen, those practices seem to be ignored when it comes to implementation for the sake of aesthetics of code, for example:
class CreateUserCommand
include UserValidator
attr_reader :repository
def initialize(repository = UserRepository.new)
@repository = repository
end
def call(user)
repository.create(user) if validate(user)
end
end
Obviously things are done this way because passing four(insert number here) different dependencies to #initialize
would make the class awkward to use, so instead we hard-code the dependency and/or widen the interface of the object with inheritance (whether it be direct inheritance or module inclusion), however, using a container we can configure this behaviour before-hand:
class Container
extend Dry::Container
namespace('user') do
register('validator') { UserValidator.new }
register('repository') { UserRepository.new }
end
end
Injector = Dry::AutoInject(Container)
class CreateUserCommand
include Injector['user.validator', 'user.repository']
def call(user)
repository.call(user) if validator.call(user).errors.empty?
end
end
This allows us to keep our interfaces narrow and stick to SRP without hard-coding dependencies or littering our codebase with four argument intializer calls.
With regards to some of the other gems, dry-types was developed due to the lack of an existing general purpose coercion library, for example, Piotr Solnica created another coercion library called Virtus, however, it is specifically geared towards coercing object attributes, is included as a module - so an object is coerced when it is initialized and it is quite slow in comparison to dry-types.
dry-validation (also created by Piotr) was created for much the same reason, as we know there are existing validation libraries out there, but from what I’ve seen, they seem to again be geared towards validating objects, are used as mixins - meaning that an object is responsible for validating itself, and they all lack a concise DSL.
dry-rb has very different philosophies to many existing libraries in the Ruby ecosystem, we try to minimise global namespace pollution (very little is introduced into the global namespace besides the Dry
module), avoid mutable state where possible - and make use of mutexes appropriately where not, compose our applications of small, dedicated objects that concentrate on performing one task and doing it well and minimise coupling.
Further reading: