Skip to content

Commit

Permalink
Include additional JSON serialization support in Guides
Browse files Browse the repository at this point in the history
  • Loading branch information
slashdotdash committed Jan 16, 2019
1 parent 9b735d4 commit b7a0ca3
Show file tree
Hide file tree
Showing 10 changed files with 32 additions and 32 deletions.
8 changes: 2 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,11 @@

```elixir
defp deps do
[
{:jason, "~> 1.1"}
]
[{:jason, "~> 1.1"}]
end
```

Jason has _no support_ for encoding arbitrary structs - explicit implementation of the `Jason.Encoder` protocol is always required.

You *must* update all your domain event modules to include `@derive Jason.Encoder` as shown below:
Jason has _no support_ for encoding arbitrary structs - explicit implementation of the `Jason.Encoder` protocol is always required. You *must* update all your domain event modules, aggregate state (when using state snapshotting), and process manager state to include `@derive Jason.Encoder` as shown below:

```elixir
defmodule AnEvent do
Expand Down
11 changes: 7 additions & 4 deletions guides/Aggregates.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,21 +228,24 @@ config :commanded, ExampleAggregate

### Snapshot serialization

Aggregate state will be serialized using the configured event store serializer, by default this stores the data as JSON. You can use the `Commanded.Serialization.JsonDecoder` protocol to decode the parsed JSON data into the expected types.
Aggregate state will be serialized using the configured event store serializer, by default this stores the data as JSON. Remember to derive the `Jason.Encoder` protocol for the aggregate state to ensure JSON serialization is supported, as shown below.

```elixir
defmodule ExampleAggregate do
@derive Jason.Encoder
defstruct [:name, :date]
end
```

You can use the `Commanded.Serialization.JsonDecoder` protocol to decode the parsed JSON data into the expected types:

```elixir
defimpl Commanded.Serialization.JsonDecoder, for: ExampleAggregate do
@doc """
Parse the date included in the aggregate state
"""
def decode(%ExampleAggregate{date: date} = state) do
%ExampleAggregate{state |
date: NaiveDateTime.from_iso8601!(date)
}
%ExampleAggregate{state | date: NaiveDateTime.from_iso8601!(date)}
end
end
```
Expand Down
5 changes: 3 additions & 2 deletions guides/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ You need to create a module per command and define the fields using `defstruct`:

```elixir
defmodule OpenAccount do
@enforce_keys [:account_number]
defstruct [:account_number, :initial_balance]
end
```

A command **must contain** a field to uniquely identify the aggregate instance (e.g. `account_number`).
A command **must contain** a field to uniquely identify the aggregate instance (e.g. `account_number`). Use `@enforce_keys` to force the identity field to be specified when creating the command struct.

Since commands are just plain Elixir structs you can use a library such as [`typed_struct`](https://hex.pm/packages/typed_struct) for defining structs, fields with their types, and enforcing mandatory keys without writing too much boilerplate code.

Expand Down Expand Up @@ -343,7 +344,7 @@ defmodule BankAccountLifespan do
def after_event(%MoneyDeposited{}), do: :timer.hours(1)
def after_event(%BankAccountClosed{}), do: :stop
def after_event(_event), do: :infinity

def after_command(%CloseAccount{}), do: :stop
def after_command(_command), do: :infinity

Expand Down
6 changes: 3 additions & 3 deletions guides/Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Domain events indicate that something of importance has occurred, within the con

Create a module per domain event and define the fields with `defstruct`. An event **should contain** a field to uniquely identify the aggregate instance (e.g. `account_number`).

Remember to derive the `Jason.Encoder` protocol for the event struct to ensure JSON serialization is supported, as shown below.

```elixir
defmodule BankAccountOpened do
@derive Jason.Encoder
Expand Down Expand Up @@ -47,11 +49,9 @@ The name given to the event handler **must be** unique and remain unchanged betw
You can choose to start the event handler's event store subscription from the `:origin`, `:current` position, or an exact event number using the `start_from` option. The default is to use the origin so your handler will receive all events.

```elixir
# start from :origin, :current, or an explicit event number (e.g. 1234)
defmodule ExampleHandler do
# Define `start_from` as one of :origin, :current, or an explicit event number (e.g. 1234)
use Commanded.Event.Handler, name: "ExampleHandler", start_from: :origin

# ...
end
```

Expand Down
1 change: 1 addition & 0 deletions guides/Process Managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ defmodule TransferMoneyProcessManager do
name: "TransferMoneyProcessManager",
router: BankRouter

@derive Jason.Encoder
defstruct [
:transfer_uuid,
:debit_account,
Expand Down
2 changes: 0 additions & 2 deletions guides/Read Model Projections.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,5 @@ defmodule MyApp.ExampleProjector do
use Commanded.Projections.Ecto,
name: "ExampleProjector",
consistency: :strong

# ...
end
```
7 changes: 4 additions & 3 deletions guides/Serialization.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Serialization

JSON serialization can be used for event and snapshot data.
JSON serialization can be used for event data, and aggregate and process manager snapshot data.

To enable JSON serialization with the included `Commanded.Serialization.JsonSerializer` module add `jason` to your deps
To enable JSON serialization with the included `Commanded.Serialization.JsonSerializer` module add `jason` to your deps:

```elixir
def deps do
Expand All @@ -16,12 +16,13 @@ The example event below has an implementation of the `Commanded.Serialization.Js

```elixir
defmodule ExampleEvent do
@derive Jason.Encoder
defstruct [:name, :date]
end

defimpl Commanded.Serialization.JsonDecoder, for: ExampleEvent do
@doc """
Parse the date included in the event
Parse the date included in the event.
"""
def decode(%ExampleEvent{date: date} = event) do
%ExampleEvent{event |
Expand Down
8 changes: 4 additions & 4 deletions guides/Supervision.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ defmodule Bank.Supervisor do

def init(:ok) do
children = [
# process manager
worker(TransferMoneyProcessManager, [[start_from: :current]], id: :transfer_money_process_manager),
# Process manager
worker(TransferMoneyProcessManager, [[start_from: :current]]),

# event handler
worker(AccountBalanceHandler, [[start_from: :origin]], id: :account_balance_handler)
# Event handler
worker(AccountBalanceHandler, [[start_from: :origin]])
]

supervise(children, strategy: :one_for_one)
Expand Down
7 changes: 3 additions & 4 deletions guides/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ When using ES/CQRS, events are first-class citizens. It's critical to be able to

Please refer to the [Testing your application](https://github.com/commanded/commanded/wiki/Testing-your-application) page on the Wiki for help with configuring your test environment.

### Additions to the wiki:
### Remember `projection_versions` when truncating tables

#### 1. Remember projection_versions when truncating tables
If you rely on your read projections in your tests, remember to truncate the projection_versions table in your truncate_readstore_tables function. Otherwise, your projector will ignore everything but the first projection.
If you rely on your read projections in your tests, remember to truncate the `projection_versions` table in your `truncate_readstore_tables/0` function. Otherwise, your projector will ignore everything but the first projection.

```elixir
defp truncate_readstore_tables do
Expand All @@ -18,7 +17,7 @@ defp truncate_readstore_tables do
table1,
table2,
table3,
projection_versions <---- REMEBER THIS LINE
projection_versions
RESTART IDENTITY
CASCADE;
"""
Expand Down
9 changes: 5 additions & 4 deletions guides/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Here's an example bank account opening feature built using Commanded to demonstr

```elixir
defmodule BankAccountOpened do
@derive Jason.Encoder
defstruct [:account_number, :initial_balance]
end
```
Expand All @@ -37,27 +38,27 @@ Here's an example bank account opening feature built using Commanded to demonstr
defmodule BankAccount do
defstruct [:account_number, :balance]

# public command API
# Public command API

def execute(%BankAccount{account_number: nil} = account, %OpenBankAccount{account_number: account_number, initial_balance: initial_balance})
when initial_balance > 0
do
%BankAccountOpened{account_number: account_number, initial_balance: initial_balance}
end

# ensure initial balance is never zero or negative
# Ensure initial balance is never zero or negative
def execute(%BankAccount{}, %OpenBankAccount{initial_balance: initial_balance})
when initial_balance <= 0
do
{:error, :initial_balance_must_be_above_zero}
end

# ensure account has not already been opened
# Ensure account has not already been opened
def execute(%BankAccount{}, %OpenBankAccount{}) do
{:error, :account_already_opened}
end

# state mutators
# State mutators

def apply(%BankAccount{} = account, %BankAccountOpened{account_number: account_number, initial_balance: initial_balance}) do
%BankAccount{account |
Expand Down

0 comments on commit b7a0ca3

Please sign in to comment.