Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
384bf7f
Fix EventData typespec
dvic Aug 9, 2022
98ac3e6
feat: put aggregate_state into assigns of the pipeline
fahchen Aug 23, 2022
88e4506
Add tag to partition test case
fabriziosestito Feb 8, 2023
19be45e
Make before_reset/0 an explicit callback
DilaksunB Jan 23, 2024
4d7e273
Update event guide to include behaviour implementation, and correct b…
DilaksunB Jan 23, 2024
c0352d3
added @impl to example app handler
DilaksunB Jan 23, 2024
a18a113
Fix refute_receive_event examples
TylerPachal May 15, 2024
5e72063
Use TypeProvider for process managers snapshot serialization
vheathen Jun 19, 2024
00869eb
doc: fix interested? function doc
lpeppe Jul 4, 2024
403db2b
fix(router.ex): Telemetry is not emitted if dispatch fails for {:erro…
Kasse-Dembele Jul 12, 2024
b124cc3
Update lib/commanded/event_store/event_data.ex
dvic Sep 18, 2024
1c247b1
Merge pull request #495 from qdentity/event-data_typespec
drteeth Sep 19, 2024
c205882
#538 Add tests to ensure a type provider called during a process mana…
vheathen Sep 22, 2024
c8f95cf
Merge pull request #557 from TylerPachal/fix_refute_receive_event_exa…
cdegroot Sep 27, 2024
ad91d37
Merge pull request #562 from lpeppe/fix-interested-doc
cdegroot Sep 27, 2024
1a7dd46
Merge pull request #502 from fahchen/feat/assign-aggregate-state
jwilger Sep 29, 2024
1fd8c24
Merge pull request #525 from fabriziosestito/tag-partition-test
jwilger Sep 29, 2024
f1ea58d
Merge pull request #550 from DilaksunB/master
drteeth Oct 13, 2024
12be4fa
Merge pull request #558 from vheathen/type_provider-for-process-managers
drteeth Oct 13, 2024
0a86ec3
Merge pull request #563 from Kasse-Dembele/master
drteeth Oct 13, 2024
bc2593a
Re-order import to satisfy credo
drteeth Oct 13, 2024
c25cb2c
Add initialize/1 handler callback
dvic Feb 15, 2023
0dd7f9d
Mark initialize/1 as defoverridable
dvic Feb 15, 2023
1f5b051
Fix flaky test
dvic Sep 20, 2024
985c0a3
Remove after_start/0 and update tests and docs
dvic Sep 20, 2024
17f0682
Mix format
dvic Sep 20, 2024
955b193
after_start/1 calls init/0 until we remove it completely
drteeth Sep 20, 2024
7f77efc
after_start/1 can also return new state
drteeth Sep 20, 2024
035a5d7
Remove serialization hack
drteeth Sep 20, 2024
37577b6
Add missing return value to after_start/1 typespec
drteeth Sep 20, 2024
6f6342c
Merge pull request #568 from bitfield-co/initialize-with-handler-state
drteeth Oct 22, 2024
f762319
Test 1.13 => 1.17 in CI
drteeth Oct 14, 2024
393757b
Re-work seralization test to allow differences in serialized key order
drteeth Oct 22, 2024
7864215
Check for unconfigured error synchronously
drteeth Oct 22, 2024
645b518
Formatting
drteeth Oct 22, 2024
0488f88
Ignore unexpected_ack errors
drteeth Oct 23, 2024
16ada7e
Update credo for 1.17
drteeth Oct 23, 2024
547deed
Merge pull request #595 from commanded/fill-out-test-matrix
drteeth Oct 23, 2024
6aaa647
Bump the version to 1.4.6 and update the changelog for release
drteeth Oct 24, 2024
116130f
Update Github actions version to the latest
drteeth Oct 24, 2024
b182252
Account for time spent in lifespan test
drteeth Oct 24, 2024
b973510
Merge pull request #598 from commanded/new-github-actions-versions
drteeth Oct 24, 2024
07feff8
Merge pull request #599 from commanded/fix-flakey-lifespan-test
drteeth Oct 30, 2024
2ff1263
feat: add router and default aggregate lifespan configuration (#548)
yordis Nov 7, 2024
4e0b668
chore: remove asdf file
yordis Sep 28, 2024
9bca887
Merge pull request #570 from yordis/chore-upgrade-asdf
drteeth Nov 29, 2024
19e7a43
Allow registration handle_call/cast callbacks to be called
drteeth Nov 30, 2024
0707962
Aggregate.handle_* now properly handles lifespans
drteeth Nov 30, 2024
5462ce5
Update dev/test deps
drteeth Dec 1, 2024
ef51d3f
Import docs from wiki
drteeth Dec 1, 2024
8d85705
chore: improve docs about aggregate version
yordis Dec 11, 2024
097ffd7
Update include_aggregate_version documentation
TylerPachal Dec 11, 2024
8ad022f
undo formatting changes
TylerPachal Dec 11, 2024
809ea78
Merge pull request #608 from yordis/improve-docs-1
drteeth Dec 12, 2024
6d1f77e
Merge pull request #609 from TylerPachal/update-include_aggregate_ver…
drteeth Dec 12, 2024
a403357
Merge pull request #606 from drteeth/add-missing-aggregate-lifespan-t…
drteeth Dec 12, 2024
bd0818e
Merge pull request #607 from commanded/allow-registration-callbacks-t…
drteeth Dec 12, 2024
067e984
First pass at expontential backup for Event Handlers
drteeth Nov 21, 2024
2207eb5
Extract error handlers into their own modules
drteeth Nov 21, 2024
2eb70d8
Troll @cdegroot
drteeth Nov 21, 2024
5316343
Obey the linter
drteeth Nov 21, 2024
b9060e4
Refactor to :on_error and functions
drteeth Nov 21, 2024
5c9564c
Formatting
drteeth Nov 21, 2024
4e65cf8
Fix call to grump
drteeth Nov 21, 2024
8ed2bdd
Obey credo
drteeth Nov 21, 2024
15f107b
Re-work error handling into configuration vs callback
drteeth Nov 22, 2024
f69af29
Remove grumpiness and on_error option
drteeth Nov 22, 2024
974ba3b
Make the call directly
drteeth Nov 22, 2024
72da606
Add tests
drteeth Nov 28, 2024
779478b
Backoff reaches a maximum of 24 hour delay
drteeth Dec 18, 2024
8b1bb3c
Update local_cluster
drteeth Dec 18, 2024
6f39cf7
Merge pull request #605 from drteeth/604-implement-exponential-backof…
drteeth Dec 18, 2024
374f93a
Merge pull request #610 from commanded/update-local_cluster
drteeth Dec 18, 2024
8fd721c
Bump version and add changelog for v1.4.7
drteeth Dec 18, 2024
d0d9698
Update telemetry libs
drteeth Dec 18, 2024
0f9124b
Add Changelog link to hex package info
drteeth Dec 18, 2024
54a6256
chore: improve aggregate state management docs
yordis Dec 21, 2024
afb79f9
Merge pull request #612 from yordis/improve-docs-2
drteeth Jan 2, 2025
f572fac
Merge pull request #611 from commanded/update-telemetry-libs
drteeth Jan 2, 2025
9954e08
Add docs for on_event_handler_error
drteeth Jan 13, 2025
42149bb
Add info about per-handler overrides
drteeth Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
name: Test

on: [push, pull_request]
on:
pull_request:
push:
branches:
- master
- 'v*'

env:
MIX_ENV: test
Expand All @@ -10,12 +15,22 @@ jobs:
name: Build and test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
otp: ['25.3']
elixir: ['1.15.7']
include:
- elixir: 1.17.x
otp: 27
- elixir: 1.16.x
otp: 26
- elixir: 1.15.x
otp: 26
- elixir: 1.14.x
otp: 26
- elixir: 1.13.x
otp: 25

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Elixir
id: beam
Expand All @@ -25,7 +40,7 @@ jobs:
otp-version: ${{ matrix.otp }}

- name: Restore dependencies cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: deps
key: ${{ runner.os }}-${{ steps.beam.outputs.otp-version }}-${{ steps.beam.outputs.elixir-version }}-mix-${{ hashFiles('**/mix.lock') }}
Expand All @@ -49,7 +64,7 @@ jobs:
mix test --include distributed

- name: Retrieve Dialyzer PLT cache
uses: actions/cache@v1
uses: actions/cache@v4
id: plt-cache
with:
path: priv/plts
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ erl_crash.dump
/priv/plts/*.plt.hash
/bench/graphs
/bench/snapshots
.tool-versions
2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# Changelog

## v1.4.7


### Enhancements

* Application-wide event handler error handling by @drteeth in https://github.com/commanded/commanded/pull/605
* chore: remove asdf file by @yordis in https://github.com/commanded/commanded/pull/570
* chore: improve docs about aggregate version by @yordis in https://github.com/commanded/commanded/pull/608
* Update include_aggregate_version documentation by @TylerPachal in https://github.com/commanded/commanded/pull/609

### Bug fixes

* Fix flakey test by @drteeth in https://github.com/commanded/commanded/pull/599
* feat: default aggregate lifespan configuration by @yordis in https://github.com/commanded/commanded/pull/548
* Aggregate.handle_* now properly handles lifespans by @drteeth in https://github.com/commanded/commanded/pull/606
* Allow registration handle_call/cast callbacks to be called by @drteeth in https://github.com/commanded/commanded/pull/607
* Update local_cluster by @drteeth in https://github.com/commanded/commanded/pull/610

## v1.4.6

### Enhancements
- Includes changelog updates
- Version bump

## v1.4.5

### Enhancements
- Support OTP 26 and Elixir 1.17 ([#595](https://github.com/commanded/commanded/pull/595)).

## v1.4.4

### Enhancements
- feat: put aggregate_state into assigns of the pipeline ([#502](https://github.com/commanded/commanded/pull/502)).
- Add tag to partition test case ([#525](https://github.com/commanded/commanded/pull/525)).
- Make before_reset/0 an explicit callback function ([#550](https://github.com/commanded/commanded/pull/550)).
- New `Event.Handler.after_start/1` callback allows configuration in the handler's process ([#568](https://github.com/commanded/commanded/pull/568)).

### Bug fixes
- Fix EventData typespec ([#495](https://github.com/commanded/commanded/pull/495)).
- Fix refute_receive_event examples ([#557](https://github.com/commanded/commanded/pull/557)).
- Fix interested? function doc ([#562](https://github.com/commanded/commanded/pull/562)).
- Use TypeProvider for process managers snapshot serialization ([#558](https://github.com/commanded/commanded/pull/558)).
- fix(router.ex): Telemetry is not emitted if dispatch fails for {:error, :unregistered_command} ([#563](in https://github.com/commanded/commanded/pull/563)).

## v1.4.3

### Enhancements
Expand Down
21 changes: 18 additions & 3 deletions guides/Aggregates.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ end

## Command functions

A command function receives the aggregate's state and the command to execute. It must return the resultant domain events, which may be one event or multiple events. You can return a single event or a list of events: `%Event{}`, `[%Event{}]`, `{:ok, %Event{}}`, or `{:ok, [%Event{}]}`.
A command function receives the aggregate's state and the command to execute. It must return the resultant domain events, which may be one event or multiple events. You can return a single event or a list of events: `%Event{}`, `[%Event{}]`, `{:ok, %Event{}}`, or `{:ok, [%Event{}]}`.

To respond without returning an event you can return `:ok`, `nil` or an empty list as either `[]` or `{:ok, []}`.

Expand All @@ -47,7 +47,7 @@ end

The state of an aggregate can only be mutated by applying a domain event to its state. This is achieved by an `apply/2` function that receives the state and the domain event. It returns the modified state.

Pattern matching is used to invoke the respective `apply/2` function for an event. These functions *must never fail* as they are used when rebuilding the aggregate state from its history of domain events. You cannot reject the event once it has occurred.
Pattern matching is used to invoke the respective `apply/2` function for an event. These functions **MUST NOT** fail as they are used when rebuilding the aggregate state from its history of domain events. You cannot reject the event once it has occurred.

```elixir
defmodule ExampleAggregate do
Expand Down Expand Up @@ -160,6 +160,20 @@ defmodule BankAccount do
end
```

> #### Effective Aggregate State {: .tip}
>
> The aggregate state should be carefully designed to maintain only the data required to:
>
> - Enforce business rules and invariants (e.g. preventing withdrawals that would overdraw an account)
> - Provide context for command handling based on previous events (e.g. checking an order's current status)
> - Make decisions that depend on historical events (e.g. verifying a refund doesn't exceed original payment)
>
> A common anti-pattern is blindly copying all event data into aggregate state without considering whether that data
> is actually needed for command handling. If a piece of state is never referenced in any command handling logic,
> it should be removed from the aggregate. This keeps the aggregate focused and maintainable.
>
> State that is only needed for querying/reporting should be maintained in read models rather than aggregate state.

## Using `Commanded.Aggregate.Multi` to return multiple events

Sometimes you need to create multiple events from a single command. You can use `Commanded.Aggregate.Multi` to help track the events and update the aggregate state. This can be useful when you want to emit multiple events that depend upon the aggregate state being updated.
Expand Down Expand Up @@ -284,8 +298,9 @@ defimpl Commanded.Serialization.JsonDecoder, for: ExampleAggregate do
end
end
```

Note: The default JSON encoding of a `DateTime` struct uses the `to_iso8601/1` function which is why we must decode it using the `from_iso8601/1` function.

### Rebuilding an aggregate snapshot

Whenever you change the structure of an aggregate's state you *must* increment the `snapshot_version` number. The aggregate state will be rebuilt from its events, ignoring any existing snapshots. They will be overwritten when the next snapshot is taken.
Whenever you change the structure of an aggregate's state you **MUST** increment the `snapshot_version` number. The aggregate state will be rebuilt from its events, ignoring any existing snapshots. They will be overwritten when the next snapshot is taken.
99 changes: 75 additions & 24 deletions guides/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ The above configuration requires that all commands for the `BankAccount` aggrega

#### Identity prefix

An optional identity prefix can be used to distinguish between different aggregates that would otherwise share the same identity. As an example you might have a `User` and a `UserPreferences` aggregate that you wish to share the same identity. In this scenario you should specify a `prefix` for each aggregate (e.g. "user-" and "user-preference-").
An optional identity prefix can be used to distinguish between different aggregates that would otherwise share the same identity. As an example you might have a `User` and a `UserPreferences` aggregate that you wish to share the same identity. In this scenario you should specify a `prefix` for each aggregate (e.g. "user-" and "user-preference-").

```elixir
defmodule BankRouter do
Expand All @@ -155,7 +155,7 @@ defmodule BankRouter do
end
```

The prefix is used as the stream identity when appending, and reading, the aggregate's events (e.g. `<prefix><instance_identity>`). Note you *must not* change the stream prefix once you have events persisted in your event store, otherwise the aggregate's events cannot be read from the event store and its state cannot be rebuilt since the stream name will be different.
The prefix is used as the stream identity when appending, and reading, the aggregate's events (e.g. `<prefix><instance_identity>`). Note you **must not** change the stream prefix once you have events persisted in your event store, otherwise the aggregate's events cannot be read from the event store and its state cannot be rebuilt since the stream name will be different.

#### Custom aggregate identity

Expand Down Expand Up @@ -231,40 +231,40 @@ end

You can choose the consistency guarantee when dispatching a command.

- *Strong consistency* offers up-to-date data but at the cost of high latency.
- *Eventual consistency* offers low latency but read model queries may reply with stale data since they may not have processed the persisted events.
- **Strong consistency** offers up-to-date data but at the cost of high latency.
- **Eventual consistency** offers low latency but read model queries may reply with stale data since they may not have processed the persisted events.

In Commanded, the available options during command dispatch are:

- `:eventual` (default) - don't block command dispatch and don't wait for any event handlers, regardless of their own consistency configuration.
- `:eventual` (default) - don't block command dispatch and don't wait for any event handlers, regardless of their own consistency configuration.

```elixir
:ok = BankApp.dispatch(command)
:ok = BankApp.dispatch(command, consistency: :eventual)
```
```elixir
:ok = BankApp.dispatch(command)
:ok = BankApp.dispatch(command, consistency: :eventual)
```

- `:strong` - block command dispatch until all strongly consistent event handlers and process managers have successfully processed all events created by the command.
- `:strong` - block command dispatch until all strongly consistent event handlers and process managers have successfully processed all events created by the command.

```elixir
:ok = BankApp.dispatch(command, consistency: :strong)
```
```elixir
:ok = BankApp.dispatch(command, consistency: :strong)
```

Dispatching a command using `:strong` consistency but without any strongly consistent event handlers configured will have no effect.
Dispatching a command using `:strong` consistency but without any strongly consistent event handlers configured will have no effect.

- Provide an explicit list of event handler and process manager modules (or their configured names), containing only those handlers you'd like to wait for. No other handlers will be awaited on, regardless of their own configured consistency setting.
- Provide an explicit list of event handler and process manager modules (or their configured names), containing only those handlers you'd like to wait for. No other handlers will be awaited on, regardless of their own configured consistency setting.

```elixir
:ok = BankApp.dispatch(command, consistency: [ExampleHandler, AnotherHandler])
:ok = BankApp.dispatch(command, consistency: ["ExampleHandler", "AnotherHandler"])
```
```elixir
:ok = BankApp.dispatch(command, consistency: [ExampleHandler, AnotherHandler])
:ok = BankApp.dispatch(command, consistency: ["ExampleHandler", "AnotherHandler"])
```

Note you cannot opt-in to strong consistency for a handler that has been configured as eventually consistent.
Note you cannot opt-in to strong consistency for a handler that has been configured as eventually consistent.

#### Which consistency guarantee should I use?

When dispatching a command using `consistency: :strong` the dispatch will block until all of the strongly consistent event handlers and process managers have handled all events created by the command. This guarantees that when you receive the `:ok` response from dispatch, your strongly consistent read models will have been updated and can safely be queried.

Strong consistency helps to alleviate problems and workarounds you would otherwise encounter when dealing with eventual consistency in your own application. Use `:strong` consistency when you want to query a read model immediately after dispatching a command. You *must* also configure the event handler to use `:strong` consistency.
Strong consistency helps to alleviate problems and workarounds you would otherwise encounter when dealing with eventual consistency in your own application. Use `:strong` consistency when you want to query a read model immediately after dispatching a command. You **must** also configure the event handler to use `:strong` consistency.

Using `:eventual` consistency, or omitting the `consistency` option, will cause the command dispatch to immediately return without waiting for any event handlers or process managers. The handlers run independently, and asynchronously, in the background, therefore you will need to deal with potentially stale read model data.

Expand Down Expand Up @@ -318,13 +318,64 @@ This is useful if you need to get information from the events produced by the ag

### Dispatch returning aggregate version

You can optionally choose to include the aggregate's version as part of the dispatch result by setting the `include_aggregate_version` option to true:
You can optionally choose to include the aggregate's version as part of the dispatch result by setting the `include_aggregate_version` option to true:

```elixir
{:ok, aggregate_version} = BankApp.dispatch(command, include_aggregate_version: true)
```

This is useful when you need to wait for an event handler, such as a read model projection, to be up-to-date before continuing execution or querying its data.
The returned `aggregate_version` can be used as an ETAG, allowing you to synchronize operations across the read-side and write-side of your application. For example, if need to wait for an event handler, such as a read model projection, to be up-to-date before continuing execution or querying its data.

```elixir
defmodule BankAccountProjector do
use Commanded.Projections.Ecto,
application: BankApp,
name: "BankAccountProjector",
repo: BankApp.Repo

project(%BankAccountOpened{} = event, metadata, fn multi ->
multi
|> Ecto.Multi.run(:bank_account, &find_bank_account(&1, &2, event.id))
|> Ecto.Multi.update(
:updated_bank_account,
&BankAccount.changeset(&1.bank_account, %{
# Notice that I am using the stream version from the metadata
# to update the auction's stream version.
stream_version: metadata.stream_version,
})
)
end)
end
```

Then you can use the aggregate version to wait for the event handler to be up-to-date:

```elixir
defmodule BankAccounts do
def get_bank_account(id, stream_version) do
query =
from b in BankAccount,
where: b.id == ^id,
where: b.stream_version >= ^stream_version,
order_by: [asc: b.stream_version]

case BankApp.one(query) do
nil ->
# not ready yet means the projection is not yet up-to-date
# so you can retry the query
{:error, :not_ready}

bank_account ->
{:ok, bank_account}
end
end
end
```

### Aggregate version as an ETAG

The aggregate version can be thought of as an ETAG (Entity Tag) for a given resource. ETAGs are commonly used in HTTP caching mechanisms to determine if a resource has changed. Similarly, the aggregate version serves as a unique identifier for a specific state of the aggregate.
When building a REST API, you can use the aggregate version as the ETAG in your HTTP responses.

### Causation and correlation ids

Expand All @@ -342,7 +393,7 @@ You can set causation and correlation ids when dispatching a command:
When dispatching a command in an event handler, you should copy these values from the metadata (second) argument associated with the event you are handling:

```elixir
defmodule ExampleHandler do
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: "ExampleHandler"
Expand Down
24 changes: 22 additions & 2 deletions guides/Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ defmodule ExampleHandler do
application: ExampleApp,
name: "ExampleHandler"

@impl Commanded.Event.Handler
def handle(%AnEvent{..}, _metadata) do
# ... process the event
:ok
Expand Down Expand Up @@ -70,6 +71,23 @@ Use the `:current` position when you don't want newly created event handlers to

You should start your event handlers using an OTP `Supervisor` to ensure they are restarted on error. See the [Supervision guide](https://hexdocs.pm/commanded/supervision.html) for more details.

### Configuration options

You can choose the default error behaviour for *all* of your event handlers in each Application's configuration:

```ellxir
config :example, ExampleApp,
on_event_handler_error: :stop # or :backoff or MyCustomErrorHandler
```

The default behaviour is to stop the event handler process when any error is encountered. As event handlers are supervised either by a custom supervisor or by the application itself, the handlers are usually restarted right away. If the error is permanent, due to a logic or data bug, then the process will likely crash again right away. This can lead the supervisor itself to give up, crash and this will continue up your supervision tree until it stops your application.

The `:backoff` option, introduced in v1.4.7, cause the even handler to retry after an exponentially increasing backoff period (up to a maximum of 24 hours). The event handler will still not be able to make forward progress until you address the issue, but it won't take your supervisors or applications down with it.

If you want to provide your own strategy, you can pass in a module that implements an `c:error/3` function that matches the `c:error/3` callback mentioned above.

It's important to note that if your event handler overrides the `error/3` callback, then that will be called instead of the application-wide strategy.

### Subscribing to an individual stream

By default event handlers will subscribe to all events appended to any stream. Provide a `subscribe_to` option to subscribe to a single stream.
Expand All @@ -87,8 +105,8 @@ This will ensure the handler only receives events appended to that stream.

### Event handler callbacks

- `c:Commanded.Event.Handler.init/0` - (optional) initialisation callback function called when the handler starts.
- `c:Commanded.Event.Handler.init/1` - (optional) used to configure the handler before it starts.
- `c:Commanded.Event.Handler.after_start/1` - (optional) initialisation callback function called in the process of the started handler.
- `c:Commanded.Event.Handler.error/3` - (optional) called when an event handle/2 callback returns an error.

### Metadata
Expand Down Expand Up @@ -120,6 +138,7 @@ defmodule ExampleHandler do
application: ExampleApp,
name: "ExampleHandler"

@impl Commanded.Event.Handler
def handle(event, metadata) do
IO.inspect(metadata)
# %{
Expand Down Expand Up @@ -199,7 +218,7 @@ end
An event handler can be reset (using a mix task), it will restart the event store subscription from the configured
`start_from`. This allow an individual handler to be restart while the app is still running.

You can define a `before_reset/1` function that will be called before resetting the event handler.
You can implement the `before_reset/0` callback that will be called before resetting the event handler.

```elixir
defmodule ExampleHandler do
Expand All @@ -211,6 +230,7 @@ defmodule ExampleHandler do

alias Commanded.Event.FailureContext

@impl Commanded.Event.Handler
def before_reset do
# Do something
:ok
Expand Down
Loading
Loading