README.md


## Overview

AshCommanded is an Elixir library that provides [Command Query Responsibility Segregation (CQRS)](https://martinfowler.com/bliki/CQRS.html) and [Event-Sourcing (ES)](https://martinfowler.com/eaaDev/EventSourcing.html) patterns for the [Ash Framework](https://hexdocs.pm/ash/). It extends Ash resources with a Commanded DSL that enables defining commands, events, and projections. The extension relies on the excellent [Commanded](https://hexdocs.pm/commanded/Commanded.html) library. The [Commanded Guides](https://hexdocs.pm/commanded/commands.html) section explains the different concepts better than I could.

Special thanks to [Ben Smith](https://github.com/slashdotdash) for the Commanded library and to [Barnabas J.] for letting me steal the library name.

## Build and Test Commands

```bash
# Install dependencies
mix deps.get

# Compile the project
mix compile

# Run all tests
mix test

# Run specific test file
mix test path/to/test_file.exs:

# Run specific test with line number
mix test path/to/test_file.exs:42:

# Run tests with coverage
mix test --cover:
```

## Architecture

AshCommanded is built as a DSL extension for [Ash Framework](https://hexdocs.pm/ash/) resources using the [Spark DSL](https://hexdocs.pm/spark/) library for its extensible DSL capabilities. Its main components are:

1. **DSL Extension**: The [`AshCommanded.Commanded.Dsl`](lib/commanded/dsl.ex) module defines five main sections:
   - [`commands`](lib/commanded/sections/commands_section.ex): Define commands that trigger state changes
   - [`events`](lib/commanded/sections/events_section.ex): Define events that are emitted by commands
   - [`projections`](lib/commanded/sections/projections_section.ex): Define how events affect the resource state
   - [`event_handlers`](lib/commanded/sections/event_handlers_section.ex): Define general purpose handlers for events
   - [`application`](lib/commanded/sections/application_section.ex): Configure Commanded application settings

2. **Code Generation**: The library dynamically generates Elixir modules:
   - Command modules (structs with typespecs)
   - Event modules (structs with typespecs)
   - Projection modules (with event handlers)
   - Projector modules (Commanded event handlers that apply projections)
   - Event handler modules (general purpose event subscribers)
   - Aggregate modules (for Commanded integration)
   - Router modules (for command dispatching)
   - Commanded application modules (with projector and handler supervision)

3. **Transformers**: The DSL uses transformers to generate code:
   - [`GenerateCommandModules`](lib/commanded/transformers/generate_command_modules.ex): Generates command structs
   - [`GenerateEventModules`](lib/commanded/transformers/generate_event_modules.ex): Generates event structs
   - [`GenerateProjectionModules`](lib/commanded/transformers/generate_projection_modules.ex): Generates projection modules
   - [`GenerateProjectorModules`](lib/commanded/transformers/generate_projector_modules.ex): Generates Commanded event handlers that process events
   - [`GenerateEventHandlerModules`](lib/commanded/transformers/generate_event_handler_modules.ex): Generates general purpose event handlers
   - [`GenerateAggregateModule`](lib/commanded/transformers/generate_aggregate_module.ex): Generates aggregate module for Commanded
   - [`GenerateDomainRouterModule`](lib/commanded/transformers/generate_domain_router_module.ex): Generates router module for each domain
   - [`GenerateMainRouterModule`](lib/commanded/transformers/generate_main_router_module.ex): Generates main application router
   - [`GenerateCommandedApplication`](lib/commanded/transformers/generate_commanded_application.ex): Generates Commanded application with projector and handler supervision

4. **Advanced Features**:
   - **Command Middleware**: Process commands through a pipeline of middleware functions
   - **Parameter Transformation**: Transform command parameters before action execution
   - **Parameter Validation**: Validate command parameters before action execution
   - **Transactional Commands**: Execute commands within database transactions
   - **Context Propagation**: Pass command, aggregate, and metadata context to actions
   - **Error Standardization**: Normalized error handling across the extension

5. **Verifiers**: Validate DSL usage:
   - Command validation (names, fields, handlers, etc.)
   - Event validation (names, fields, etc.)
   - Projection validation (events, actions, changes, etc.)
   - Event handler validation (events, actions, etc.)

## Usage Example

```elixir
defmodule ECommerce.Customer do
  use Ash.Resource,
    extensions: [AshCommanded.Commanded.Dsl]

  attributes do
    uuid_primary_key :id
    attribute :name, :string
    attribute :email, :string
    attribute :status, :atom, constraints: [one_of: [:pending, :active]]
  end

  identities do
    identity :unique_id, [:id]
  end

  actions do
    defaults [:read]

    create :register do
      accept [:name, :email]
      change {Ash.Changeset, :set_attribute, [:status, :pending]}
    end

    update :confirm_email do
      accept []
      change {Ash.Changeset, :set_attribute, [:status, :active]}
    end
  end

  commanded do
    commands do
      command :register_customer do
        fields([:id, :name, :email])
        identity_field(:id)
        action :register
      end

      command :confirm_email do
        fields([:id])
        identity_field(:id)
        action :confirm_email
      end
    end

    events do
      event :customer_registered do
        fields([:id, :name, :email])
      end

      event :email_confirmed do
        fields([:id])
      end
    end

    projections do
      projection :customer_registered do
        action(:create)
        changes(%{
          status: :pending
        })
      end

      projection :email_confirmed do
        action(:update_by_id)
        changes(%{
          status: :active
        })
      end
    end
    
    event_handlers do
      handler :notification_handler do
        events [:customer_registered]
        action fn event, _metadata ->
          ECommerce.Notifications.send_welcome_email(event.email)
          :ok
        end
      end
      
      handler :analytics_tracker do
        events [:customer_registered, :email_confirmed]
        action fn event, _metadata ->
          ECommerce.Analytics.track(event)
          :ok
        end
      end
    end
  end
end
```

This will generate:
- `ECommerce.Commands.RegisterCustomer` - Command struct
- `ECommerce.Events.CustomerRegistered` - Event struct
- `ECommerce.Projections.CustomerRegistered` - Projection definition
- `ECommerce.Projectors.CustomerProjector` - Commanded event handler for projections
- `ECommerce.EventHandlers.CustomerNotificationHandler` - General purpose event handler
- `ECommerce.EventHandlers.CustomerAnalyticsTrackerHandler` - General purpose event handler
- `ECommerce.CustomerAggregate` - Aggregate module
- `ECommerce.Store.Router` - Domain-specific router (if in an Ash.Domain)
- `AshCommanded.Router` - Main application router

## Documentation

AshCommanded provides comprehensive documentation that can be generated locally:

```bash
# Install dependencies
mix deps.get

# Generate cheatsheet and docs
mix gen.docs
```

The documentation includes:
- Guides for commands, events, projections, event handlers, and routers
- Guides for middleware, parameter handling, transactions, and context propagation
- API reference for all modules
- Cheatsheets for the DSL

Additional documentation files:
- [Commands](commands.html)
- [Events](events.html)
- [Projections](projections.html)
- [Event Handlers](event_handlers.html)
- [Middleware](middleware.html)
- [Parameter Handling](parameter_handling.html)
- [Transactions](transactions.html)
- [Context Propagation](context_propagation.html)
- [Error Handling](error_handling.html)
- [Application](application.html)
- [Routers](routers.html)
- [Snapshotting](snapshotting.html)

## Commands

Commands define the actions that can be performed on your resources. AshCommanded generates command modules as structs with typespecs.

```elixir
commanded do
  commands do
    command :register_customer do
      fields([:id, :name, :email])
      identity_field(:id)
      action :register
    end

    command :confirm_email do
      fields([:id])
      identity_field(:id)
      action :confirm_email
    end
  end
end
```

Generated command modules include:

- A struct with the specified fields
- Typespecs for all fields
- Standard module documentation

Example generated command:
```elixir
defmodule ECommerce.Commands.RegisterCustomer do
  @moduledoc """
  Command for registering a new customer
  """
  
  @type t :: %__MODULE__{
    id: String.t(),
    email: String.t(),
    name: String.t(),
    status: atom()
  }
  
  defstruct [:id, :email, :name, :status]
end
```

## Command Handlers

Command handlers are modules that process commands and apply business logic. AshCommanded generates handler modules that invoke Ash actions.

```elixir
defmodule AshCommanded.Commanded.CommandHandlers.CustomerHandler do
  @behaviour Commanded.Commands.Handler
  
  def handle(%ECommerce.Commands.RegisterCustomer{} = cmd, _metadata) do
    Ash.run_action(ECommerce.Customer, :register, Map.from_struct(cmd))
  end
  
  def handle(%ECommerce.Commands.ConfirmEmail{} = cmd, _metadata) do
    Ash.run_action(ECommerce.Customer, :confirm_email, Map.from_struct(cmd))
  end
end
```

Handler options:
- `handler_name` - Custom function name for the handler clause
- `action` - Specify a different Ash action to call (defaults to command name)
- `autogenerate_handler?` - Set to false to disable handler generation

## Middleware, Parameter Handling, and Transactions

AshCommanded provides advanced features for command processing:

### Middleware

Command middleware allows you to intercept and modify commands before they are executed:

```elixir
commanded do
  commands do
    # Apply middleware to all commands in this resource
    middleware AuditLogger
    middleware {Authorization, roles: [:admin]}
    
    command :register_customer do
      fields([:id, :name, :email])
      # Command-specific middleware
      middleware {RateLimiter, limit: 10}
    end
  end
end
```

### Parameter Transformation and Validation

You can transform and validate command parameters before action execution:

```elixir
command :create_order do
  fields([:id, :items, :customer_id, :total])
  
  transform_params do
    map item_ids: :items
    compute :timestamp, &DateTime.utc_now/0
    cast :total, :decimal
  end
  
  validate_params do
    validate :total, number: [greater_than: 0]
    validate :items, present: true
  end
end
```

### Transaction Support

Execute commands within database transactions:

```elixir
command :place_order do
  fields [:id, :customer_id, :items]
  
  # Use inline transaction options
  in_transaction? true
  repo MyApp.Repo
  transaction_timeout 5000
  transaction_isolation_level :serializable
  
  # Or use block syntax
  transaction do
    enabled? true
    repo MyApp.Repo
    timeout 5000
    isolation_level :read_committed
  end
end
```

### Context Propagation

Control how command context is passed to actions:

```elixir
command :register_customer do
  fields [:id, :name, :email]
  
  # Context options
  include_aggregate? true
  include_command? true
  include_metadata? true
  context_prefix :cmd
  static_context %{source: :registration_api}
end
```

## Events

Events represent facts that have occurred in your system. AshCommanded generates event modules as structs with typespecs.

```elixir
commanded do
  events do
    event :customer_registered do
      fields([:id, :name, :email])
    end

    event :email_confirmed do
      fields([:id])
    end
  end
end
```

Generated event modules include:
- A struct with the specified fields
- Typespecs for all fields
- Standard module documentation

Example generated event:
```elixir
defmodule ECommerce.Events.CustomerRegistered do
  @moduledoc """
  Event emitted when a customer is registered
  """
  
  @type t :: %__MODULE__{
    id: String.t(),
    email: String.t(),
    name: String.t(),
    status: atom()
  }
  
  defstruct [:id, :email, :name, :status]
end
```

## Aggregates and Events-Handlers

Aggregates process events and update state. AshCommanded generates aggregate modules for each resource.
Each event that mutate state is handled by the Aggregate via an apply function that is automatically generated for you.
```elixir
defmodule ECommerce.CustomerAggregate do
  defstruct [:id, :email, :name, :status]
  
  # Apply event to update the aggregate state
  def apply(%__MODULE__{} = state, %ECommerce.Events.CustomerRegistered{} = event) do
    %__MODULE__{
      state |
      id: event.id,
      email: event.email,
      name: event.name
    }
  end
  
  def apply(%__MODULE__{} = state, %ECommerce.Events.EmailConfirmed{} = event) do
    %__MODULE__{state | status: :active}
  end
end
```

The aggregate maintains the current state by applying events in sequence. Each event handler updates specific fields based on the event data.

## Projections

Projections define how events should update your read models. AshCommanded generates projection modules that handle specific event types.

```elixir
commanded do
  projections do
    projection :customer_registered do
      action(:create)
      changes(%{
        status: :pending
      })
    end

    projection :email_confirmed do
      action(:update_by_id)
      changes(%{
        status: :active
      })
    end
  end
end
```

Projection options:
- `action` - The Ash action to perform (`:create`, `:update`, `:destroy`, etc.)
- `changes` - Static map or function that returns the changes to apply
- `autogenerate?` - Set to false to disable projection generation

## Event Handlers

Event handlers define how to respond to domain events with side effects, integrations, notifications, or other operations. Unlike projections which focus on updating read models, event handlers are for operations that don't necessarily affect resource state.

```elixir
commanded do
  event_handlers do
    # Function-based handler for sending notifications
    handler :welcome_notification do
      events [:customer_registered]
      action fn event, _metadata ->
        ECommerce.Notifications.send_welcome_email(event.email)
        :ok
      end
    end
    
    # Handler with multiple events
    handler :analytics_tracker do
      events [:customer_registered, :email_confirmed]
      action fn event, _metadata ->
        ECommerce.Analytics.track(event)
        :ok
      end
    end
    
    # Handler using an Ash action
    handler :external_system_sync do
      events [:customer_registered]
      action :sync_to_crm
      idempotent true
    end
    
    # PubSub broadcasting handler
    handler :event_broadcaster do
      events [:customer_registered, :email_confirmed]
      publish_to "customer_events"
    end
  end
end
```

Event handler options:
- `events` - List of event names this handler will respond to
- `action` - Action to perform when handling the event (atom or function)
- `handler_name` - Override the auto-generated handler module name
- `publish_to` - PubSub topic(s) to publish the event to
- `idempotent` - Whether the handler is idempotent (default: false)
- `autogenerate?` - Set to false to disable handler generation

Generated event handler modules handle the specified events and execute the defined actions or functions:

```elixir
defmodule ECommerce.EventHandlers.CustomerWelcomeNotificationHandler do
  use Commanded.Event.Handler,
    application: ECommerce.CommandedApplication,
    name: "ECommerce.EventHandlers.CustomerWelcomeNotificationHandler"
  
  def handle(%ECommerce.Events.CustomerRegistered{} = event, _metadata) do
    ECommerce.Notifications.send_welcome_email(event.email)
    :ok
  end
end
```

## Projectors

Projectors are Commanded event handlers that listen for domain events and update read models. AshCommanded automatically generates projector modules using the `GenerateProjectorModules` transformer. These projectors:

1. Subscribe to specific event types defined in your resource
2. Process events using the Commanded event handling system
3. Apply changes to your resources via Ash actions (create, update, destroy)

For example, a generated projector might look like:

```elixir
defmodule ECommerce.Projectors.CustomerProjector do
  use Commanded.Projections.Ecto, name: "ECommerce.Projectors.CustomerProjector"

  project(%ECommerce.Events.CustomerRegistered{} = event, _metadata, fn _context ->
    Ash.Changeset.new(ECommerce.Customer, event)
    |> Ash.Changeset.for_action(:create, %{status: :pending})
    |> Ash.create()
  end)
  
  project(%ECommerce.Events.EmailConfirmed{} = event, _metadata, fn _context ->
    Ash.Changeset.new(ECommerce.Customer, %{id: event.id})
    |> Ash.Changeset.for_action(:update, %{status: :active})
    |> Ash.update()
  end)
  
  # Functions to apply 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
```

You can customize the projector name with the `projector_name` option or disable automatic generation with `autogenerate?: false`.

## Router Usage

The generated routers allow dispatching commands to their appropriate handlers:

```elixir
# Dispatch a command through the main router
command = %ECommerce.Commands.RegisterCustomer{id: "123", email: "customer@example.com", name: "John Doe"}
AshCommanded.Router.dispatch(command)
```

## Commanded Application

The `application` section in the DSL allows configuring a Commanded application at the domain level:

```elixir
defmodule ECommerce.Store do
  use Ash.Domain, extensions: [AshCommanded.Commanded.Dsl]

  resources do
    resource ECommerce.Product
    resource ECommerce.Customer
    resource ECommerce.Order
  end

  commanded do
    application do
      otp_app :ecommerce
      event_store Commanded.EventStore.Adapters.EventStore
      include_supervisor? true
    end
  end
end
```

This generates a Commanded application module that:
- Configures the event store and other Commanded settings
- Includes the domain router
- Provides a supervisor for all projectors
- Can be added to your application's supervision tree

## Where are the Process Managers?

Process Managers in Commanded are responsible for coordinating one or more aggregates. They handle events and dispatch commands in response. This is very business logic specific and would be rather difficult to generate appropriately. It is suggested to write your Process Managers using [Reactor](https://hexdocs.pm/reactor/readme.html) instead, which is a library specifically designed for workflow orchestration in Elixir and works well with Commanded's event-driven architecture.