# Spear
![CI](https://github.com/NFIBrokerage/spear/workflows/CI/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/NFIBrokerage/spear/badge.svg)](https://coveralls.io/github/NFIBrokerage/spear)
[![hex.pm version](https://img.shields.io/hexpm/v/spear.svg)](https://hex.pm/packages/spear)
[![hex.pm license](https://img.shields.io/hexpm/l/spear.svg)](https://github.com/NFIBrokerage/spear/blob/master/LICENSE)
[![Last Updated](https://img.shields.io/github/last-commit/NFIBrokerage/spear.svg)](https://github.com/NFIBrokerage/spear/commits/main)
A sharp EventStoreDB 20+ client backed by mint :yum:
## FAQ
**Why the name "spear"?**
1. best gum flavor
1. obligatory programmer reference to ancient greek, roman, or egyptian history
1. sounds cool :sunglasses:
**Backed by... Mint?**
[`elixir-mint/mint`](https://github.com/elixir-mint/mint) is a functional
HTTP client which supports HTTP2.
As the kids say these days, gRPC is spicy HTTP2. Practically speaking,
gRPC just adds some well-known headers and a message format that allows
messages to not be aligned with HTTP2 DATA frames. It's relatively trivial
to implement gRPC with a nice HTTP2 library like mint :slightly_smiling_face:.
**Why not [`elixir-grpc/grpc`](https://github.com/elixir-grpc/grpc)?**
That project looks good but it depends on
[`:gun`](https://github.com/ninenines/gun) which doesn't play nice with
other dependencies. It also provides a server and client implementation in
one library. This library only needs a client.
<!--
Wanted to keep this #shade out of the online+viewable readme:
Also the code hygiene is... questionable 🤔
https://github.com/elixir-grpc/grpc/blob/eff8a8828d27ddd7f63a3c1dd5aae86246df215e/lib/grpc/adapter/gun.ex#L170-L262
-->
**Does TLS work?**
Yep! As of v0.1.3, custom and public CAs may be used for encrypted connections.
**Does this work with EventStore <20?**
Sadly no. This library only provides a gRPC client which showed up in
EventStoreDB 20+. If you're looking for a similarly fashioned TCP client,
NFIBrokerage uses
[`exponentially/extreme`](https://github.com/exponentially/extreme) extensively
in production (specifically the v1.0.0 branch). Spear and Extreme have
compatible dependencies and similar styles of making connections.
**How many dependencies are we talking here?**
Spear's reliance on Mint and `:gpb` give it a very small dependency tree with
no transitive dependencies! The full tree is:
```
$ mix deps.tree --only prod
spear
├── connection ~> 1.0 (Hex package)
├── gpb ~> 4.0 (Hex package)
├── jason >= 0.0.0 (Hex package)
└── mint ~> 1.0 (Hex package)
```
(And `jason` is optional!)
**How close is this to being able to be used?**
Check out the roadmap in [#7](https://github.com/NFIBrokerage/spear/issues/7)
## Installation
Add `:spear` to your mix dependencies in `mix.exs`
```elixir
def deps do
[
{:spear, "~> 0.1"},
# If you want to encode events as JSON, :jason is a great library for
# encoding and decoding and works out-of-the-box with spear.
# Any JSON (de)serializer should work though, so you don't *need* to add
# :jason to your dependencies.
{:jason, "~> 1.0"},
# If you're connecting to an EventStoreDB with a TLS certificate signed
# by a public Certificate Authority (CA), include :castore
{:castore, ">= 0.0.0"}
]
end
```
## Usage
Familiar with [`Ecto.Repo`](https://hexdocs.pm/ecto/Ecto.Repo.html)? It lets
you write a database connection like a module
```elixir
# note this is for illustration purposes and NOT directly related to Spear
# lib/my_app/repo.ex
defmodule MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
end
```
and then configure it with application-config (`config/*.exs`)
```elixir
# note this is for illustration purposes and NOT directly related to Spear
# config/config.exs
config :my_app, MyApp.Repo,
url: "ecto://postgres:postgres@localhost/my_database"
```
Spear lets you do the same with a connection to the EventStoreDB:
```elixir
# lib/my_app/event_store_db_client.ex
defmodule MyApp.EventStoreDbClient do
use Spear.Client,
otp_app: :my_app
end
```
and configure it,
```elixir
# config/config.exs
config :my_app, MyApp.EventStoreDbClient,
connection_string: "esdb://localhost:2113"
```
add it to your application's supervision tree in `lib/my_app/application.ex`
```elixir
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
MyApp.EventStoreDbClient
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
end
```
And now you're ready to use spear! Check out the [Spear
documentation](https://hexdocs.pm/spear/Spear.html).
<details><summary>Alternate usages...</summary>
<br>
A `Spear.Connection` is just a regular ole' GenServer with a default of pulling
configuration from application-config. You can start a `Spear.Connection`
like any other process, even in IEx! Plus you can provide the configuration
straight to the `Spear.Connection.start_link/1` function.
Let's use the new `Mix.install/1` function from Elixir 1.12 to try out
Spear. Say that you have an EventStoreDB instance running locally with the
`--insecure` option.
```elixir
iex> Mix.install([:spear, :jason])
# a bunch of installation text here
:ok
iex> {:ok, conn} = Spear.Connection.start_link(connection_string: "esdb://localhost:2113")
{:ok, #PID<0.1518.0>}
iex> event = Spear.Event.new("IExAndSpear", %{"hello" => "world"})
%Spear.Event{
body: %{"hello" => "world"},
id: "9e3a8bcf-0c22-4a38-85c6-2054a0342ec8",
metadata: %{content_type: "application/json", custom_metadata: ""},
type: "IExAndSpear"
}
iex> [event] |> Spear.append(conn, "MySpearDemo")
:ok
iex> Spear.stream!(conn, "MySpearDemo")
#Stream<[
enum: #Function<62.80860365/2 in Stream.unfold/2>,
funs: [#Function<48.80860365/1 in Stream.map/2>]
]>
iex> Spear.stream!(conn, "MySpearDemo") |> Enum.to_list()
[
%Spear.Event{
body: %{"hello" => "world"},
id: "9e3a8bcf-0c22-4a38-85c6-2054a0342ec8",
metadata: %{
commit_position: 18446744073709551615,
content_type: "application/json",
created: ~U[2021-04-12 20:05:17.757215Z],
custom_metadata: "",
prepare_position: 18446744073709551615,
stream_name: "MySpearDemo",
stream_revision: 0
},
type: "IExAndSpear"
}
]
```
And we're up and running reading and writing events!
</details>