# Context Propagation
AshCommanded provides comprehensive context propagation between commands and actions, allowing for rich information sharing throughout the command execution pipeline.
## Overview
Context propagation enables:
1. Access to the aggregate state within action execution
2. Access to the original command in actions
3. Metadata sharing between commands and actions
4. Custom static context values
5. Flexible context key prefixing to prevent collisions
This mechanism ensures that actions have full access to the command execution context, improving traceability and enabling more sophisticated behavior.
## Configuring Context Propagation
Context propagation can be configured at the command level in the `commanded` DSL:
```elixir
defmodule MyApp.User do
use Ash.Resource,
extensions: [AshCommanded.Commanded.Dsl]
commanded do
commands do
command :register_user do
fields [:id, :email, :name]
identity_field :id
# Context propagation options
include_aggregate? true
include_command? true
include_metadata? true
context_prefix :cmd
static_context %{source: :web_api}
end
end
end
end
```
## Context Options
The following options control context propagation:
- `include_aggregate?` - Whether to include the aggregate state in the action context (default: `true`)
- `include_command?` - Whether to include the command struct in the action context (default: `true`)
- `include_metadata?` - Whether to include command metadata in the action context (default: `true`)
- `context_prefix` - An optional prefix for context keys to prevent collisions (default: `nil`)
- `static_context` - Static context values to include in every action execution (default: `%{}`)
## Context Structure
By default, the following context keys are included:
- `:aggregate` - The current state of the aggregate
- `:command` - The command being executed
- `:metadata` - Command metadata (if present)
When a `context_prefix` is specified, the keys become:
- `:"prefix.aggregate"` - The current state of the aggregate
- `:"prefix.command"` - The command being executed
- `:"prefix.metadata"` - Command metadata (if present)
Any values in `static_context` are merged directly into the context map.
## Accessing Context in Actions
Within your Ash actions, you can access the context as follows:
```elixir
defmodule MyApp.UserActions do
def create_user(changeset) do
# Get context from the changeset
context = Ash.Changeset.get_context(changeset)
# Access aggregate state (if included)
aggregate = Map.get(context, :aggregate)
# Access command (if included)
command = Map.get(context, :command)
# Access metadata (if included)
metadata = Map.get(context, :metadata)
# Access static context values
source = Map.get(context, :source)
# Use context data in your action logic
# ...
changeset
end
end
```
With a `context_prefix`, you would access the keys with the prefix:
```elixir
# With context_prefix: :cmd
aggregate = Map.get(context, :"cmd.aggregate")
command = Map.get(context, :"cmd.command")
metadata = Map.get(context, :"cmd.metadata")
```
## Context and Event Generation
When commands are processed, the action result can influence the generated event:
```elixir
defmodule MyApp.UserActions do
def create_user(changeset) do
# Return additional data to include in the event
# This will be merged with the command fields
{:ok, %{registered_at: DateTime.utc_now()}}
end
end
```
The additional map returned by the action will be merged with the command fields when generating the event, allowing for dynamic event enrichment.
## Context in Middleware
Command middleware also has access to the context, enabling powerful pattern like:
- Audit logging using command, metadata, and aggregate state
- Authorization based on command and metadata
- Dynamic command transformation based on context
- Context-aware validation
## Example: Using Context in Practice
```elixir
defmodule MyApp.User do
use Ash.Resource,
extensions: [AshCommanded.Commanded.Dsl]
attributes do
uuid_primary_key :id
attribute :email, :string
attribute :name, :string
attribute :status, :string
attribute :created_at, :utc_datetime
end
actions do
create :create do
accept [:id, :email, :name]
change create_timestamp()
end
update :update_email do
accept [:email]
change update_timestamp()
end
end
# Action helpers using context
defp create_timestamp(changeset) do
context = Ash.Changeset.get_context(changeset)
source = Map.get(context, :source, :unknown)
changeset
|> Ash.Changeset.change_attribute(:created_at, DateTime.utc_now())
|> Ash.Changeset.put_context(:creation_source, source)
end
defp update_timestamp(changeset) do
context = Ash.Changeset.get_context(changeset)
command = Map.get(context, :command)
timestamp = DateTime.utc_now()
changeset
|> Ash.Changeset.change_attribute(:updated_at, timestamp)
|> Ash.Changeset.put_metadata(:last_change, %{
field: :email,
from: Map.get(context.aggregate || %{}, :email),
to: command.email,
timestamp: timestamp
})
end
commanded do
commands do
command :register_user do
fields [:id, :email, :name]
identity_field :id
action :create
static_context %{source: :registration_form}
end
command :update_email do
fields [:id, :email]
identity_field :id
action :update_email
context_prefix :user
end
end
events do
event :user_registered do
fields [:id, :email, :name, :created_at]
end
event :email_updated do
fields [:id, :email, :updated_at]
end
end
end
end
```
## Benefits of Context Propagation
1. **Richer Domain Logic** - Actions can incorporate command and aggregate information
2. **Improved Auditing** - Full command context for better traceability
3. **Better Event Generation** - Events can include action-specific data
4. **Reduced Duplication** - No need to repeat data already in commands or aggregates
5. **Context-Aware Behavior** - Actions can adapt based on context information
## Best Practices
1. Use `context_prefix` when integrating with other extensions to avoid key collisions
2. Keep the `static_context` map small and focused on truly static values
3. Use `include_aggregate?` and `include_command?` selectively for large objects
4. Document which context keys your actions depend on
5. Consider using middleware for cross-cutting concerns rather than action-specific context handling