# Projections
Projections in AshCommanded define how events affect the state of your resources. They are the read model update mechanism in the [CQRS pattern](https://martinfowler.com/bliki/CQRS.html). In Commanded, [projections](https://hexdocs.pm/commanded/read-model-projections.html) transform domain events into a read-optimized format.
## Defining Projections
Projections are defined in the `commanded` DSL extension for Ash resources:
```elixir
defmodule ECommerce.Customer do
use Ash.Resource,
extensions: [AshCommanded.Commanded.Dsl]
attributes do
uuid_primary_key :id
attribute :email, :string
attribute :name, :string
attribute :status, :string
end
commanded do
events do
event :customer_registered do
fields([:id, :email, :name])
end
event :customer_status_updated do
fields([:id, :status])
end
end
projections do
projection :customer_registered do
action(:create)
changes(%{
status: "pending"
})
end
projection :customer_status_updated do
action(:update_by_id)
changes(&Map.take(&1, [:status]))
end
end
end
end
```
## Projection Options
Each projection can have the following options:
- `action`: The Ash action to use when handling the event (`:create`, `:update`, etc.)
- `changes`: Static map or function to determine the changes to apply
- `projector_name`: Override the generated projector module name
## Generated Projector Modules
AshCommanded generates a projector module for each resource with projections. This projector is a [Commanded event handler](https://hexdocs.pm/commanded/Commanded.Event.Handler.html) that subscribes to events and updates the read model. It leverages [Commanded's projection support](https://hexdocs.pm/commanded/read-model-projections.html) to handle the events efficiently.
```elixir
defmodule ECommerce.Projectors.CustomerProjector do
@moduledoc """
Projector for Customer-related events
"""
use Commanded.Projections.Ecto,
name: "ECommerce.Projectors.CustomerProjector"
# See Commanded's Ecto projections: https://hexdocs.pm/commanded_ecto_projections/
# Each projection gets a project/3 function
project(%ECommerce.Events.CustomerRegistered{} = event, _metadata, fn _context ->
Ash.Changeset.new(ECommerce.Customer, event)
|> Ash.Changeset.for_action(:create, %{
id: event.id,
email: event.email,
name: event.name,
status: "pending"
})
|> Ash.create()
end)
project(%ECommerce.Events.CustomerStatusUpdated{} = event, _metadata, fn _context ->
Ash.Changeset.new(ECommerce.Customer, event)
|> Ash.Changeset.for_action(:update_by_id, %{
status: event.status
})
|> Ash.update()
end)
# Helper functions for applying different action types
defp apply_action_fn(:create), do: &Ash.create/1
defp apply_action_fn(:update), do: &Ash.update/1
defp apply_action_fn(:destroy), do: &Ash.destroy/1
end
```
## Registering Projectors with Commanded
The generated projectors need to be registered with your Commanded application to start processing events. Add them to your application's supervisor tree:
```elixir
defmodule ECommerce.Application do
use Application
def start(_type, _args) do
children = [
# ...other children
# Start your projectors
ECommerce.Projectors.CustomerProjector
]
opts = [strategy: :one_for_one, name: ECommerce.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
For more complex projection needs, you can customize the `project/3` function directly in your own projector module:
```elixir
defmodule ECommerce.CustomProjector do
use Commanded.Projections.Ecto, name: "ECommerce.CustomProjector"
project(%ECommerce.Events.CustomerRegistered{} = event, metadata, fn _context ->
# Access event and metadata
customer_id = event.id
timestamp = metadata.created_at
# Custom projection logic
Ash.Changeset.new(ECommerce.Customer)
|> Ash.Changeset.for_action(:create, %{
id: customer_id,
email: event.email,
name: event.name,
status: "pending",
registered_at: timestamp
})
|> Ash.create()
end)
end
```
Each resource typically gets one projector module containing handlers for all projections. The projector:
1. Automatically registers with Commanded to receive events
2. Processes events in real-time as they occur
3. Updates the read models (resources) using Ash actions
4. Maintains consistency between write and read models
## Customizing Projector Generation
You can customize the projector generation with several options:
```elixir
projection :user_registered do
# Specify the action to perform (create, update, destroy)
action(:create)
# Changes to apply
changes(%{status: "active"})
# Custom projector module name
projector_name(:CustomUserProjector)
# Disable projector generation for this projection
autogenerate?(false)
end
```
For a resource with many projectors, you can also set the projector namespace:
```elixir
defmodule ECommerce.Customer do
use Ash.Resource,
extensions: [AshCommanded.Commanded.Dsl]
# Set custom namespace for all projectors
@projector_namespace ECommerce.CustomProjectors
# Resource definition...
end
```
## Change Types
The `changes` option supports two formats:
1. Static map - simple key-value changes:
```elixir
changes(%{
status: "active"
})
```
2. Function - dynamic changes based on the event:
```elixir
changes(fn event ->
%{
status: event.status,
last_updated: DateTime.utc_now()
}
end)
```
Or more concisely with capture syntax:
```elixir
changes(&Map.take(&1, [:status]))
```