README.md

# Commanded.Middleware.Uniqueness

A [Commanded](https://github.com/commanded/commanded) [middleware](https://hexdocs.pm/commanded/commands.html#middleware) for checking certain values uniqueness during commands dispatch. Might be useful as a short-term unique values cache before subsequent events persisted and projected.

Based on the [Ben Smith](https://github.com/slashdotdash)'s idea described in his "Building Conduit" [book](https://leanpub.com/buildingconduit).

[Please check the latest published CommandedMiddlewareUniqueness release documentation on Hex](https://hexdocs.pm/commanded_uniqueness_middleware/).

## Installation

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

```elixir
def deps do
  [
    {:commanded_uniqueness_middleware, "~> 0.7.0"}
  ]
end
```

## Configuration

Define options in config/config.exs as:

```elixir
  config :commanded_uniqueness_middleware,
    adapter: Commanded.Middleware.Uniqueness.Adapter.Cachex,
    # ttl: 60 minutes in seconds
    ttl: 60 * 60,
    use_command_as_partition: false
```

or

```elixir
  config :commanded_uniqueness_middleware,
    adapter: Commanded.Middleware.Uniqueness.Adapter.Nebulex,
    nebulex_cache: YourApp.Nebulex.Uniqueness.Cache,
    # ttl: 60 minutes in seconds
    ttl: 60 * 60,
    use_command_as_partition: false
```

where:
  - `:adapter` is an Uniqueness adapter implemented `Commanded.Middleware.Uniqueness.Adapter` behavior,
  - `:nebulex_cache` is a module with Nebulex.Cache behaviour implemented in your application,
  - `:ttl` is claimed value time-to-live,
  - `:use_command_as_partition` should be set to true to use each command module name as partition. Use with  caution! If neither this nor Unique protocol `:partition` option defined then `Commanded.Middleware.Uniqueness` value used as a partition name.

## Adapters
Two adapters currently exist:
- Based on Cachex `Commanded.Middleware.Uniqueness.Adapter.Cachex`
- Based on Nebula `Commanded.Middleware.Uniqueness.Adapter.Nebulex`

Any adapter implementing `Commanded.Middleware.Uniqueness.Adapter` behavior can be used.

### Nebulex
Nebulex adapter requires a custom Nebulex cache module. Please create it inside your application like:

```elixir
  mix nbx.gen.cache -c YourApp.Nebulex.Uniqueness.Cache
```

You can configure Nebulex caching strategy in the your application config and/or in the cache module itself.
For details please check [Nebulex documentation](https://hexdocs.pm/nebulex/getting-started.html#adding-nebulex-to-an-application)

## Usage
Imagine you have an aggregate with a unique field value requirement, for example, it might be a `:username` field. You've got a new user and issue a `RegisterUser` command with `SomeCoolUsername` `:name` field value. The command successfully went through all checks and spawned a UserRegistered event but this event hasn't been projected yet. At this very moment an another user wants to register with the same name, and as the previous event isn't projected you have no information that this user name 
has been taken.

You can use `Commanded.Middleware.Uniqueness` to ensure that your system will not get into a conflict state in between two commands.

It utilizes [`Elixir Protocol`](https://hexdocs.pm/elixir/Protocol.html).

You need to put 
```elixir
middleware Commanded.Middleware.Uniqueness
```
into your Commanded Router as described in [Commanded docs](https://hexdocs.pm/commanded/commands.html#middleware).

Then you need to define a `Commanded.Middleware.Uniqueness.UniqueFields` implementation for the specific command:

```elixir
defmodule MyApp.RegisterUser do
  defstruct [
    :id,
    :name,
    :email
  ]
end

defimpl Commanded.Middleware.Uniqueness.UniqueFields, for: MyApp.RegisterUser do
  def unique(%MyApp.RegisterUser{id: id}),
    do: [
      {:name, "has already been taken", id, ignore_case: true, is_unique: &is_taken_externally?/4}
    ]

  def is_taken_externally?(_field, value, _owner, _opts), do: !String.starts_with?(value, "ExternallyTaken")
end
```

At the first command dispatch the Uniqueness Middleware checks the `:name` field key - value pair is 
free and claims it for the given owner id.

If you need to release previously claimed value with existing TTL you should use `release/4`, `release_by_owner/3` or `release_by_value/3` adapter methods:

```elixir
defmodule MyApp.UserNameCacheHandler do
  use Commanded.Event.Handler,
    application: MyApp.App,
    name: "UserNameCacheHandler"

  alias MyApp.UserDeleted

  def handle(%UserDeleted{id: id}, _metadata) do
    :ok = Commanded.Middleware.Uniqueness.release_by_owner(:name, id)
    end
  end
end
```

To get to know behavior you can check modules documentation and tests (especially commands described in `test/support`).