Component Providers
External dependencies can be provided as bootable components, these components can be shared across applications with the ability to configure them and customize booting process. This is especially useful in situations where you have a set of applications with many common components.
Bootable components are handled by component providers, which can register themselves and set up their components. Let's say we want to provide a common exception notifier for many applications. First, we register our provider called :common:
# my_gem
# |- lib/my_gem/components.rb
Dry::System.register_provider(
:common,
boot_path: Pathname(__dir__).join('boot').realpath()
)
Then we define our component:
# my_gem
# |- lib/my_gem/boot/exception_notifier.rb
Dry::System.register_component(:exception_notifier, provider: :common) do
init do
require "some_exception_notifier"
end
start do
register(:exception_notifier, SomeExceptionNotifier.new)
end
end
Now in application container we can easily boot this external component:
# system/app/container.rb
require "dry/system/container"
require "my_gem/components"
module App
class Container < Dry::System::Container
boot(:exception_notifier, from: :common)
end
end
App::Container[:exception_notifier]
Hooking into booting process
You can use lifecycle before/after callbacks if you need to do something special. For instance, you may want to customize object registration, for this you can use after(:start) callback, which receives a container that was set up by your :common component provider:
module App
class Container < Dry::System::Container
boot(:exception_notifier, from: :common) do
after(:start) do |common|
register(:notifier, common[:exception_notifier])
end
end
end
end
Following callbacks are supported:
before(:init)after(:init)before(:start)after(:start)
Providing component configuration
Components can specify their configuration settings using settings block, settings specify keys and types, and default values can be set too. If a component uses settings, then lifecycle steps have access to its config.
Here's an extended :exception_notifier example which uses its own settings:
# my_gem
# |- lib/my_gem/boot/exception_notifier.rb
Dry::System.register_component(:exception_notifier, provider: :common) do
settings do
key :environments, Types::Strict::Array.of(Types::Strict::Symbol).default(%i[production])
key :logger, Types::Any
end
init do
require "some_exception_notifier"
end
start do
# now we have access to `config`
register(:exception_notifier, SomeExceptionNotifier.new(config.to_h))
end
end
In this example we define two config keys:
:environmentswhich is a list of environment identifiers with default value set to[:production]:loggeran object that should be used as the logger, which must be configured
In order to configure our :logger we simply use configure block when registering the component:
module App
class Container < Dry::System::Container
boot(:exception_notifier, from: :common) do
after(:init) do
require "logger"
end
configure do |config|
config.logger = Logger.new($stdout)
end
end
end
end