Providers are a way to register components with your containers, outside of the automatic registration mechanism detailed in containers and components.
Providers are useful when:
- you want to register a specific instance of an object as a component, and have that very same instance be available as a dependency
- you need to set up a dependency that requires non-trivial configuration (often a third party library, or some library-like code in your
- you want to take advantage of provider lifecycle methods (prepare, start and stop)
Providers should be placed in the
config/providers directory. Here’s an example provider for that registers a client for an imagined third-party Acme Email delivery service.
# config/providers/email_client.rb Hanami.app.register_provider(:email_client) do prepare do require "acme_email/client" end start do client = AcmeEmail::Client.new( api_key: target["settings"].acme_api_key, default_from: "firstname.lastname@example.org" ) register "email_client", client end end
The above provider creates an instance of Acme’s email client, using an API key from our application’s settings, then registers the client in the app container with the key
The registered dependency can now become a dependency for other components, via
# app/operations/send_welcome_email.rb module Bookshelf module Operations class SendWelcomeEmail include Deps[ "email_client", "renderers.welcome_email" ] def call(name:, email_address:) email_client.deliver( to: email_address, subject: "Welcome!", text_body: welcome_email.render_text(name: name), html_body: welcome_email.render_html(name: name) ) end end end end
Every provider has a name (
Hanami.app.register_provider(:my_provider_name)) and will usually register one or more related components with the relevant container.
Registered components can be any kind of object - they can be classes too.
To register an item with the container, providers call
register, which takes two arguments: the key to be used, and the item to register under it.
# config/providers/my_provider.rb Hanami.app.register_provider(:my_provider) do start do register "my_thing", MyThing.new register "another.thing", AnotherThing.new register "thing", Thing end end
Providers offer a three-stage lifecycle:
stop. Each has a distinct purpose:
- prepare - basic setup code, here you can require third-party code, or code from your
libdirectory, and perform basic configuration
- start - code that needs to run for a component to be usable at runtime
- stop - code that needs to run to stop a component, perhaps to close a database connection, or purge some artifacts.
# config/providers/database.rb Hanami.app.register_provider(:database) do prepare do require "acme/db" register "database", Acme::DB.configure(target["settings"].database_url) end start do target["database"].establish_connection end stop do target["database"].close_connection end end
A provider’s prepare and start steps will run as necessary when a component that the provider registers is used by another component at runtime.
start on each of your application’s providers, meaning each of your providers is started automatically when your application boots. Similarly,
Hanami.shutdown can be invoked to call
stop on each provider.
You can also trigger lifecycle transitions directly by using
Accessing the container via
Within a provider, the
target method (also available as
target_container) can be used to access the app container.
This is useful if your provider needs to use other components within the container, for example the application’s settings or logger (via
target["logger"]). It can also be used when a provider wants to ensure another provider has started before starting itself, via
Hanami.app.register_provider(:uploads_bucket) do prepare do require "aws-sdk-s3" end start do target.start(:metrics) uploads_bucket_name = target["settings"].uploads_bucket_name credentials = Aws::Credentials.new( target["settings"].aws_access_key_id, target["settings"].aws_secret_access_key, ) uploads_bucket = Aws::S3::Resource.new(credentials: credentials).bucket(uploads_bucket_name) register "uploads_bucket", uploads_bucket end end