diff --git a/docsite/source/container.html.md b/docsite/source/container.html.md
index 63e2709c..a31046aa 100644
--- a/docsite/source/container.html.md
+++ b/docsite/source/container.html.md
@@ -2,6 +2,8 @@
title: Container
layout: gem-single
name: dry-system
+sections:
+ - hooks
---
The main API of dry-system is the abstract container that you inherit from. It allows you to configure basic settings and exposes APIs for requiring files easily. Container is the entry point to your application, and it encapsulates application's state.
diff --git a/docsite/source/container/hooks.html.md b/docsite/source/container/hooks.html.md
new file mode 100644
index 00000000..34312e9d
--- /dev/null
+++ b/docsite/source/container/hooks.html.md
@@ -0,0 +1,78 @@
+---
+title: Hooks
+layout: gem-single
+name: dry-system
+---
+
+There are a few lifecycle events that you can hook into if you need to ensure things happen in a particular order.
+
+Hooks are executed within the context of the container instance.
+
+### `configure` Event
+
+You can register a callback to fire after the container is configured, which happens one of three ways:
+
+1. The `configure` method is called on the container
+2. The `configured!` method is called
+3. The `finalize!` method is called when neither of the other two have been
+
+```ruby
+class MyApp::Container < Dry::System::Container
+ after(:configure) do
+ # do something here
+ end
+end
+```
+
+### `register` Event
+
+Most of the time, you will know what keys you are working with ahead of time. But for certain cases you may want to
+react to keys dynamically.
+
+```ruby
+class MyApp::Container < Dry::System::Container
+ use :monitoring
+
+ after(:register) do |key|
+ next unless key.end_with?(".gateway")
+
+ monitor(key) do |event|
+ resolve(:logger).debug(key:, method: event[:method], time: event[:time])
+ end
+ end
+end
+```
+
+Now let's say you register `api_client.gateway` into your container. Your API methods will be automatically monitored
+and their timing measured and logged.
+
+### `finalize` Event
+
+Finalization is the point at which the container is made ready, such as booting a web application.
+
+The following keys are loaded in sequence:
+
+1. Providers
+2. Auto-registered components
+3. Manually-registered components
+4. Container imports
+
+At the conclusion of this process, the container is frozen thus preventing any further changes. This makes the
+`finalize` event quite important: it's the last call before your container will disallow mutation.
+
+Unlike the previous events, you can register before hooks in addition to after hooks.
+
+The after hooks will run immediately prior to the container freeze. This allows you to enumerate the container keys
+while they can still be mutated, such as with `decorate` or `monitor`.
+
+```ruby
+class MyApp::Container < Dry::System::Container
+ before(:finalize) do
+ # Before system boot, no keys registered yet
+ end
+
+ after(:finalize) do
+ # After system boot, all keys registered
+ end
+end
+```
diff --git a/lib/dry/system/container.rb b/lib/dry/system/container.rb
index 3afd7687..89be5b21 100644
--- a/lib/dry/system/container.rb
+++ b/lib/dry/system/container.rb
@@ -325,10 +325,10 @@ def finalize!(freeze: true, &)
[providers, auto_registrar, manifest_registrar, importer].each(&:finalize!)
@__finalized__ = true
-
- self.freeze if freeze
end
+ self.freeze if freeze
+
self
end
@@ -484,6 +484,15 @@ def root
config.root
end
+ # @api public
+ def register(key, *)
+ super
+
+ hooks[:after_register].each { |hook| instance_exec(key, &hook) }
+
+ self
+ end
+
# @api public
def resolve(key)
load_component(key) unless finalized?
diff --git a/spec/unit/container/hooks/after_hooks_spec.rb b/spec/unit/container/hooks/after_hooks_spec.rb
new file mode 100644
index 00000000..8bae351b
--- /dev/null
+++ b/spec/unit/container/hooks/after_hooks_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+RSpec.describe Dry::System::Container do
+ subject(:system) do
+ Class.new(described_class)
+ end
+
+ describe "after_register hook" do
+ it "executes after a new key is registered" do
+ expect { |hook|
+ system.after(:register, &hook)
+ system.register(:foo) { "bar" }
+ }.to yield_with_args(:foo)
+ end
+
+ it "provides the fully-qualified key" do
+ expect { |hook|
+ system.after(:register, &hook)
+ system.namespace :foo do
+ register(:bar) { "baz" }
+ end
+ }.to yield_with_args("foo.bar")
+ end
+ end
+
+ describe "after_finalize hook" do
+ it "executes after finalization" do
+ expect { |hook|
+ system.after(:finalize, &hook)
+ system.finalize!
+ }.to yield_control
+ end
+
+ it "executes before the container is frozen" do
+ is_frozen = nil
+
+ system.after(:finalize) { is_frozen = frozen? }
+ system.finalize!
+
+ expect(is_frozen).to eq false
+ expect(system).to be_frozen
+ end
+ end
+end