documentation/snapshotting.md

# Aggregate Snapshotting

AshCommanded supports [aggregate snapshotting](https://hexdocs.pm/commanded/aggregate-snapshotting.html) as a performance optimization for aggregates with many events. Snapshotting allows the system to store the current state of an aggregate at specific points and then only replay events that occurred after the snapshot was taken. This implementation follows the [Commanded snapshotting architecture](https://hexdocs.pm/commanded/aggregate-snapshotting.html).

## Why Use Snapshotting?

In event-sourced systems, aggregates rebuild their state by replaying all events from the beginning of time. This can become a performance issue for aggregates that have accumulated a large number of events over their lifetime.

Snapshotting addresses this by:

1. Capturing the aggregate state at a point in time
2. Storing this snapshot for future use
3. Only loading events that occurred after the snapshot was taken

This significantly improves performance for aggregates with many events.

## Enabling Snapshotting

Snapshotting is configured at the application level in your domain:

```elixir
defmodule MyApp.Domain do
  use Ash.Domain
  
  resources do
    resource MyApp.User
  end
  
  commanded do
    application do
      otp_app :my_app
      event_store Commanded.EventStore.Adapters.EventStore
      
      # Enable snapshotting
      snapshotting true
      
      # Take a snapshot every 100 events (default)
      snapshot_threshold 100
      
      # Snapshot schema version (for future schema evolution)
      snapshot_version 1
      
      # Optional custom snapshot store module
      # snapshot_store MyApp.CustomSnapshotStore
    end
  end
end
```

## How Snapshotting Works

When snapshotting is enabled, the following happens (following Commanded's [snapshot loading process](https://hexdocs.pm/commanded/aggregate-snapshotting.html#how-it-works)):

1. **Command Execution**: When a command is sent to an aggregate, the system first checks if a snapshot exists for that aggregate.

2. **Snapshot Loading**: If a snapshot exists, the aggregate state is restored from the snapshot rather than rebuilding from scratch.

3. **Event Loading**: Only events that occurred after the snapshot's version are loaded and applied to the aggregate.

4. **Snapshot Creation**: After applying events, the system checks if a new snapshot should be taken based on the configured threshold.

## Snapshot Storage

By default, snapshots are stored in memory using an ETS table, similar to Commanded's [in-memory snapshot store](https://hexdocs.pm/commanded/aggregate-snapshotting.html#snapshot-storage). This is suitable for development but not for production. 

In a production environment, you should implement a custom snapshot store that persists snapshots to a database or other storage system, following Commanded's [snapshot adapter pattern](https://hexdocs.pm/commanded/aggregate-snapshotting.html#custom-snapshot-storage).

### Custom Snapshot Store

You can implement a custom snapshot store by creating a module that implements the `AshCommanded.Commanded.SnapshotStore` behaviour, which is based on Commanded's [Snapshot adapter](https://hexdocs.pm/commanded/Commanded.Snapshotting.Adapter.html) interface:

```elixir
defmodule MyApp.CustomSnapshotStore do
  @behaviour AshCommanded.Commanded.SnapshotStore
  
  # Get a snapshot by aggregate ID and type
  @impl true
  def get_snapshot(source_uuid, source_type) do
    # Implementation that retrieves from database
  end
  
  # Save a snapshot
  @impl true
  def save_snapshot(snapshot) do
    # Implementation that saves to database
  end
  
  # Delete all snapshots for an aggregate
  @impl true
  def delete_snapshots(source_uuid, source_type) do
    # Implementation that deletes from database
  end
  
  # Initialize the store
  @impl true
  def init(config) do
    # Implementation that initializes the store
  end
end
```

Then configure your domain to use it:

```elixir
commanded do
  application do
    # ... other settings ...
    snapshot_store MyApp.CustomSnapshotStore
  end
end
```

## Performance Considerations

- **Threshold**: The `snapshot_threshold` setting controls how frequently snapshots are taken. A lower value means more snapshots but potentially better performance for frequently accessed aggregates.

- **State Size**: Snapshots store the entire aggregate state, which can be large. Consider the storage requirements when enabling snapshotting.

- **Asynchronous Snapshots**: Snapshots are created asynchronously to avoid impacting command execution performance.

## Snapshot Format Evolution

The `snapshot_version` setting allows for future schema evolution. If you need to change the structure of your aggregates, you can increment this version number and add migration logic in your aggregate's `restore_from_snapshot/1` function.

## Example

```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
  end
  
  commanded do
    commands do
      command :register_user do
        fields [:id, :email, :name]
        identity_field :id
      end
      
      command :change_email do
        fields [:id, :email]
        identity_field :id
      end
    end
    
    events do
      event :user_registered do
        fields [:id, :email, :name]
      end
      
      event :email_changed do
        fields [:id, :email]
      end
    end
  end
end
```

With snapshotting enabled, after registering many users and changing emails multiple times, the system will automatically create snapshots when needed, improving command execution performance.