Repository: commanded/commanded
Pull request #444 (2021-05-12 21:36:35)

Dispatch returning resultant events
Extend command dispatch `returning` option to support `:events` to return the resultant domain events.

### Example usage

```elixir
{:ok, events} = BankApp.dispatch(command, returning: :events)
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 510e813..d6fa03a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
 - Graceful shutdown of event handlers ([#431](https://github.com/commanded/commanded/pull/431)).
 - Ensure command dispatch metadata is a map ([#432](https://github.com/commanded/commanded/pull/432)).
 - Retry command execution on node down ([#429](https://github.com/commanded/commanded/pull/429)).
+- Dispatch returning resultant events ([#444](https://github.com/commanded/commanded/pull/444)).
 
 ## v1.2.0
 
diff --git a/lib/commanded/aggregates/execution_context.ex b/lib/commanded/aggregates/execution_context.ex
index b69a177..3c0266e 100644
--- a/lib/commanded/aggregates/execution_context.ex
+++ b/lib/commanded/aggregates/execution_context.ex
@@ -86,6 +86,9 @@ defmodule Commanded.Aggregates.ExecutionContext do
         :aggregate_version ->
           {:ok, aggregate_version, events, aggregate_version}
 
+        :events ->
+          {:ok, aggregate_version, events, events}
+
         :execution_result ->
           result = %ExecutionResult{
             aggregate_uuid: aggregate_uuid,
diff --git a/lib/commanded/commands/router.ex b/lib/commanded/commands/router.ex
index b7dac6f..9be938c 100644
--- a/lib/commanded/commands/router.ex
+++ b/lib/commanded/commands/router.ex
@@ -145,6 +145,9 @@ defmodule Commanded.Commands.Router do
 
     - `:aggregate_version` - to return only the aggregate version.
 
+    - `:events` - to return the resultant domain events. An empty list will be
+      returned if no events were produced.
+
     - `:execution_result` - to return a `Commanded.Commands.ExecutionResult`
       struct containing the aggregate's identity, state, version, and any events
       produced from the command along with their associated metadata.
@@ -436,6 +439,9 @@ defmodule Commanded.Commands.Router do
             - `:aggregate_version` - to include the aggregate stream version
               in the successful response: `{:ok, aggregate_version}`.
 
+            - `:events` - to return the resultant domain events. An empty list
+              will be returned if no events were produced.
+
             - `:execution_result` - to return a `Commanded.Commands.ExecutionResult`
               struct containing the aggregate's identity, version, and any
               events produced from the command along with their associated
@@ -535,6 +541,7 @@ defmodule Commanded.Commands.Router do
               (returning = Keyword.get(opts, :returning)) in [
                 :aggregate_state,
                 :aggregate_version,
+                :events,
                 :execution_result,
                 false
               ] ->
@@ -607,6 +614,7 @@ defmodule Commanded.Commands.Router do
       (default_dispatch_return = get_opt(opts, :default_dispatch_return)) in [
         :aggregate_state,
         :aggregate_version,
+        :events,
         :execution_result,
         false
       ] ->


Test patch:
diff --git a/test/commands/dispatch_return_test.exs b/test/commands/dispatch_return_test.exs
index 18ebb26..8edc3b4 100644
--- a/test/commands/dispatch_return_test.exs
+++ b/test/commands/dispatch_return_test.exs
@@ -4,9 +4,14 @@ defmodule Commanded.Commands.DispatchReturnTest do
   alias Commanded.Commands.ExecutionResult
   alias Commanded.ExampleDomain.BankApp
   alias Commanded.ExampleDomain.BankAccount
-  alias Commanded.ExampleDomain.BankAccount.Commands.DepositMoney
-  alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
-  alias Commanded.ExampleDomain.BankAccount.Events.BankAccountOpened
+  alias Commanded.ExampleDomain.BankAccount.Commands.{CloseAccount, DepositMoney, OpenAccount}
+
+  alias Commanded.ExampleDomain.BankAccount.Events.{
+    BankAccountClosed,
+    BankAccountOpened,
+    MoneyDeposited
+  }
+
   alias Commanded.Helpers.CommandAuditMiddleware
 
   setup do
@@ -25,8 +30,7 @@ defmodule Commanded.Commands.DispatchReturnTest do
     test "should return an error on failure" do
       command = %OpenAccount{account_number: "ACC123", initial_balance: -1}
 
-      assert {:error, :invalid_initial_balance} ==
-               BankApp.dispatch(command, returning: false)
+      assert {:error, :invalid_initial_balance} == BankApp.dispatch(command, returning: false)
     end
   end
 
@@ -76,6 +80,64 @@ defmodule Commanded.Commands.DispatchReturnTest do
     end
   end
 
+  describe "dispatch return events" do
+    test "should return resultant events" do
+      assert {:ok, events} =
+               BankApp.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1_000},
+                 returning: :events
+               )
+
+      assert match?(
+               [
+                 %BankAccountOpened{account_number: "ACC123", initial_balance: 1_000}
+               ],
+               events
+             )
+
+      assert {:ok, events} =
+               BankApp.dispatch(
+                 %DepositMoney{account_number: "ACC123", amount: 100},
+                 returning: :events
+               )
+
+      assert match?(
+               [
+                 %MoneyDeposited{
+                   account_number: "ACC123",
+                   transfer_uuid: _transfer_uuid,
+                   amount: 100,
+                   balance: 1_100
+                 }
+               ],
+               events
+             )
+    end
+
+    test "should return empty list when no events produced" do
+      assert {:ok, _events} =
+               BankApp.dispatch(%OpenAccount{account_number: "ACC123", initial_balance: 1},
+                 returning: :events
+               )
+
+      assert {:ok, events} =
+               BankApp.dispatch(%CloseAccount{account_number: "ACC123"}, returning: :events)
+
+      assert match?(
+               [%BankAccountClosed{account_number: "ACC123"}],
+               events
+             )
+
+      assert {:ok, []} =
+               BankApp.dispatch(%CloseAccount{account_number: "ACC123"}, returning: :events)
+    end
+
+    test "should return an error on failure" do
+      command = %OpenAccount{account_number: "ACC123", initial_balance: -1}
+
+      assert {:error, :invalid_initial_balance} == BankApp.dispatch(command, returning: :events)
+    end
+  end
+
   describe "dispatch return execution result" do
     test "should return created events" do
       metadata = %{"ip_address" => "127.0.0.1"}
diff --git a/test/example_domain/bank_router.ex b/test/example_domain/bank_router.ex
index 44e2943..b090f6a 100644
--- a/test/example_domain/bank_router.ex
+++ b/test/example_domain/bank_router.ex
@@ -4,6 +4,7 @@ defmodule Commanded.ExampleDomain.BankRouter do
   use Commanded.Commands.Router
 
   alias Commanded.ExampleDomain.BankAccount
+  alias Commanded.ExampleDomain.BankAccount.Commands.CloseAccount
   alias Commanded.ExampleDomain.BankAccount.Commands.DepositMoney
   alias Commanded.ExampleDomain.BankAccount.Commands.OpenAccount
   alias Commanded.ExampleDomain.BankAccount.Commands.WithdrawMoney
@@ -22,6 +23,7 @@ defmodule Commanded.ExampleDomain.BankRouter do
   dispatch OpenAccount, to: OpenAccountHandler, aggregate: BankAccount
   dispatch DepositMoney, to: DepositMoneyHandler, aggregate: BankAccount
   dispatch WithdrawMoney, to: WithdrawMoneyHandler, aggregate: BankAccount
+  dispatch CloseAccount, to: OpenAccountHandler, aggregate: BankAccount
 
   # Money transfer
   identify MoneyTransfer, by: :transfer_uuid