Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple Commanded apps #4

Merged
merged 5 commits into from Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -6,3 +6,4 @@

erl_crash.dump
*.ez
.elixir_ls
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## Next release

- Support multiple Commanded apps ([#4](https://github.com/commanded/commanded-eventstore-adapter/pull/4/files)).

## 0.6.0

### Enhancements
Expand Down
20 changes: 15 additions & 5 deletions LICENSE
@@ -1,8 +1,18 @@
The MIT License (MIT)
Copyright (c) 2017 Ben Smith (ben@10consulting.com)
The MIT License (MIT) Copyright (c) 2017 Ben Smith (ben@10consulting.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
58 changes: 9 additions & 49 deletions README.md
Expand Up @@ -8,52 +8,12 @@ MIT License

[![Build Status](https://travis-ci.com/commanded/commanded-eventstore-adapter.svg?branch=master)](https://travis-ci.com/commanded/commanded-eventstore-adapter)

## Getting started

The package can be installed from hex as follows.

1. Add `commanded_eventstore_adapter` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:commanded_eventstore_adapter, "~> 0.6"}]
end
```

2. Include `:eventstore` in the list of extra applications to start in `mix.exs`:

```elixir
def application do
[
extra_applications: [
:logger,
:eventstore,
],
]
end
```

3. Configure Commanded to use the `Commanded.EventStore.Adapters.EventStore` adapter:

```elixir
config :commanded,
event_store_adapter: Commanded.EventStore.Adapters.EventStore
```

4. Configure the `eventstore` in each environment's mix config file (e.g. `config/dev.exs`), specifying usage of the included JSON serializer:

```elixir
config :eventstore, EventStore.Storage,
serializer: Commanded.Serialization.JsonSerializer,
username: "postgres",
password: "postgres",
database: "eventstore_dev",
hostname: "localhost",
pool_size: 10
```

5. Create the `eventstore` database and tables using the `mix` task:

```console
$ mix do event_store.create, event_store.init
```
---

> This README and the following guides follow the `master` branch which may not be the currently published version.
>
> [Read the documentation for the latest published version of Commanded EventStore adapter on Hex](https://hexdocs.pm/commanded_eventstore_adapter/).

### Overview

- [Getting started](guides/Getting%20Started.md)
4 changes: 3 additions & 1 deletion config/test.exs
Expand Up @@ -9,7 +9,9 @@ config :commanded,
assert_receive_event_timeout: 1_000,
refute_receive_event_timeout: 1_000

config :eventstore, EventStore.Storage,
config :commanded_eventstore_adapter, event_stores: [TestEventStore]

config :commanded_eventstore_adapter, TestEventStore,
serializer: Commanded.Serialization.JsonSerializer,
username: "postgres",
password: "postgres",
Expand Down
57 changes: 57 additions & 0 deletions guides/Getting Started.md
@@ -0,0 +1,57 @@
# Getting started

The package can be installed from hex as follows.

1. Add `commanded_eventstore_adapter` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:commanded_eventstore_adapter, "~> 0.6"}]
end
```

2. Create an event store for your application:

```elixir
defmodule MyApp.EventStore do
use EventStore, otp_app: :my_app
end
```

3. Define and configure your Commanded application to use the `Commanded.EventStore.Adapters.EventStore` adapter and your own event store module:

```elixir
defmodule MyApp.Application do
use Commanded.Application,
otp_app: :my_app,
event_store: [
adapter: Commanded.EventStore.Adapters.EventStore,
event_store: MyApp.EventStore
]
end
```

4. Configure the event store in each environment's mix config file (e.g. `config/dev.exs`), specifying usage of the included JSON serializer:

```elixir
config :my_app, MyApp.EventStore,
serializer: Commanded.Serialization.JsonSerializer,
username: "postgres",
password: "postgres",
database: "eventstore_dev",
hostname: "localhost",
pool_size: 10
```

5. Add your event store to `config/config.exs` to make it easier to use the event store mix tasks:

```elixir
# config/config.exs
config :my_app, event_stores: [MyApp.EventStore]
```

6. Create the `eventstore` database and tables using the `mix` task:

```console
$ mix do event_store.create, event_store.init
```
105 changes: 76 additions & 29 deletions lib/event_store_adapter.ex
@@ -1,8 +1,5 @@
defmodule Commanded.EventStore.Adapters.EventStore do
@moduledoc """
[EventStore](https://github.com/commanded/eventstore) adapter for
[Commanded](https://github.com/commanded/commanded).
"""
@moduledoc false

alias Commanded.EventStore.Adapters.EventStore.Mapper

Expand All @@ -11,45 +8,60 @@ defmodule Commanded.EventStore.Adapters.EventStore do
@all_stream "$all"

@impl Commanded.EventStore
def child_spec, do: []
def child_spec(event_store, config) do
event_store = get_event_store({event_store, config})

verify_event_store!(event_store)

[event_store]
end

@impl Commanded.EventStore
def append_to_stream(stream_uuid, expected_version, events) do
EventStore.append_to_stream(
def append_to_stream(event_store, stream_uuid, expected_version, events) do
event_store = get_event_store(event_store)

event_store.append_to_stream(
stream_uuid,
expected_version,
Enum.map(events, &Mapper.to_event_data/1)
)
end

@impl Commanded.EventStore
def stream_forward(stream_uuid, start_version \\ 0, read_batch_size \\ 1_000) do
case EventStore.stream_forward(stream_uuid, start_version, read_batch_size) do
def stream_forward(event_store, stream_uuid, start_version \\ 0, read_batch_size \\ 1_000) do
event_store = get_event_store(event_store)

case event_store.stream_forward(stream_uuid, start_version, read_batch_size) do
{:error, error} -> {:error, error}
stream -> Stream.map(stream, &Mapper.from_recorded_event/1)
end
end

@impl Commanded.EventStore
def subscribe(:all), do: subscribe(@all_stream)
def subscribe(event_store, :all), do: subscribe(event_store, @all_stream)

@impl Commanded.EventStore
def subscribe(stream_uuid) do
EventStore.subscribe(stream_uuid, mapper: &Mapper.from_recorded_event/1)
def subscribe(event_store, stream_uuid) do
event_store = get_event_store(event_store)
event_store.subscribe(stream_uuid, mapper: &Mapper.from_recorded_event/1)
end

@impl Commanded.EventStore
def subscribe_to(:all, subscription_name, subscriber, start_from) do
EventStore.subscribe_to_all_streams(
def subscribe_to(event_store, :all, subscription_name, subscriber, start_from) do
event_store = get_event_store(event_store)

event_store.subscribe_to_all_streams(
subscription_name,
subscriber,
subscription_options(start_from)
)
end

@impl Commanded.EventStore
def subscribe_to(stream_uuid, subscription_name, subscriber, start_from) do
EventStore.subscribe_to_stream(
def subscribe_to(event_store, stream_uuid, subscription_name, subscriber, start_from) do
event_store = get_event_store(event_store)

event_store.subscribe_to_stream(
stream_uuid,
subscription_name,
subscriber,
Expand All @@ -58,44 +70,51 @@ defmodule Commanded.EventStore.Adapters.EventStore do
end

@impl Commanded.EventStore
def ack_event(subscription, %Commanded.EventStore.RecordedEvent{} = event) do
def ack_event(event_store, subscription, %Commanded.EventStore.RecordedEvent{} = event) do
%Commanded.EventStore.RecordedEvent{event_number: event_number} = event

EventStore.ack(subscription, event_number)
event_store = get_event_store(event_store)
event_store.ack(subscription, event_number)
end

@impl Commanded.EventStore
def unsubscribe(subscription) do
def unsubscribe(_event_store, subscription) do
EventStore.Subscriptions.Subscription.unsubscribe(subscription)
end

@impl Commanded.EventStore
def delete_subscription(:all, subscription_name) do
EventStore.delete_subscription(@all_stream, subscription_name)
def delete_subscription(event_store, :all, subscription_name) do
event_store = get_event_store(event_store)
event_store.delete_subscription(@all_stream, subscription_name)
end

@impl Commanded.EventStore
def delete_subscription(stream_uuid, subscription_name) do
EventStore.delete_subscription(stream_uuid, subscription_name)
def delete_subscription(event_store, stream_uuid, subscription_name) do
event_store.delete_subscription(stream_uuid, subscription_name)
end

@impl Commanded.EventStore
def read_snapshot(source_uuid) do
with {:ok, snapshot_data} <- EventStore.read_snapshot(source_uuid) do
def read_snapshot(event_store, source_uuid) do
event_store = get_event_store(event_store)

with {:ok, snapshot_data} <- event_store.read_snapshot(source_uuid) do
{:ok, Mapper.from_snapshot_data(snapshot_data)}
end
end

@impl Commanded.EventStore
def record_snapshot(%Commanded.EventStore.SnapshotData{} = snapshot) do
def record_snapshot(event_store, %Commanded.EventStore.SnapshotData{} = snapshot) do
event_store = get_event_store(event_store)

snapshot
|> Mapper.to_snapshot_data()
|> EventStore.record_snapshot()
|> event_store.record_snapshot()
end

@impl Commanded.EventStore
def delete_snapshot(source_uuid) do
EventStore.delete_snapshot(source_uuid)
def delete_snapshot(event_store, source_uuid) do
event_store = get_event_store(event_store)
event_store.delete_snapshot(source_uuid)
end

defp subscription_options(start_from) do
Expand All @@ -104,4 +123,32 @@ defmodule Commanded.EventStore.Adapters.EventStore do
mapper: &Mapper.from_recorded_event/1
]
end

defp get_event_store({_event_store, config}), do: Keyword.get(config, :event_store)

defp verify_event_store!(event_store) do
unless event_store do
raise ArgumentError,
"missing :event_store option for event store adapter in application"
end

unless Code.ensure_compiled?(event_store) do
raise ArgumentError,
"event store #{inspect(event_store)} was not compiled, " <>
"ensure it is correct and it is included as a project dependency"
end

unless implements?(event_store, EventStore) do
raise ArgumentError,
"module #{inspect(event_store)} is not an EventStore, " <>
"ensure you pass an event store module to the :event_store config in application"
end
end

# Returns `true` if module implements behaviour.
defp implements?(module, behaviour) do
behaviours = Keyword.take(module.__info__(:attributes), [:behaviour])

[behaviour] in Keyword.values(behaviours)
end
end
19 changes: 11 additions & 8 deletions mix.exs
Expand Up @@ -34,18 +34,18 @@ defmodule Commanded.EventStore.Adapters.EventStore.Mixfile do
]
end

defp elixirc_paths(_), do: ["lib"]
defp elixirc_paths(_env), do: ["lib"]

defp deps do
[
{:commanded, ">= 0.19.0", runtime: Mix.env() == :test},
{:eventstore, ">= 0.17.0"},
{:commanded, github: "commanded/commanded"},
{:eventstore, github: "commanded/eventstore"},

# Optional dependencies
{:jason, "~> 1.1", optional: true},

# Build & test tools
{:ex_doc, "~> 0.20", only: :dev},
{:ex_doc, "~> 0.21", only: :dev},
{:mix_test_watch, "~> 0.9", only: :dev},
{:mox, "~> 0.5", only: :test}
]
Expand All @@ -59,9 +59,13 @@ defmodule Commanded.EventStore.Adapters.EventStore.Mixfile do

defp docs do
[
main: "Commanded.EventStore.Adapters.EventStore",
main: "Getting-Started",
canonical: "http://hexdocs.pm/commanded_eventstore_adapter",
source_ref: "v#{@version}"
source_ref: "v#{@version}",
extras: [
{"guides/Getting Started.md", title: "EventStore adapter"},
"CHANGELOG.md"
]
]
end

Expand All @@ -76,8 +80,7 @@ defmodule Commanded.EventStore.Adapters.EventStore.Mixfile do
maintainers: ["Ben Smith"],
licenses: ["MIT"],
links: %{
"GitHub" => "https://github.com/commanded/commanded-eventstore-adapter",
"Docs" => "https://hexdocs.pm/commanded_eventstore_adapter/"
"GitHub" => "https://github.com/commanded/commanded-eventstore-adapter"
}
]
end
Expand Down