Code Organization for an Elixir Endpoint
Tags: elixir
Published: 2023-02-15 20:39:22

This is just my thoughts on how I would organize code in an endpoint. Let's have an example `addresses` endpoint that will handle _CRUD_ operations for customer `addresses` in a store.

For brevety we will only focus on a simple query that returns all the addresses for a given customer id.

The structure is based on [CRC](https://redrapids.medium.com/learning-elixir-its-all-reduce-204d05f52ee7) and some loose interpretation of the concepts in [Designing Elixir Systems with OTP](https://pragprog.com/titles/jgotp/designing-elixir-systems-with-otp/)

## Directory Structure

```txt
├── addresses
│   ├── endpoints.ex
│   ├── metrics.ex
│   ├── repo
│   │   ├── commands.ex
│   │   └── queries.ex
│   ├── requests.ex
│   └── resolvers.ex
├── metrics.ex
├── requests.ex
└── responses.ex
```

## Root Directory

### requests.ex

Let's start with the `requests.ex` file that will standarize the params given to the endpoints and use it as a token (accumulator) to pass between the reducers. This would be a `Constructor` in CRC.

```elixir
defmodule Endpoints.Requests do
  alias Endpoints.Metrics

  defstruct [params: %{}, metrics: Metrics.new(), data: %{}, valid?: true]

  def new(metrics, params, data \\ %{}, valid? \\ true) do
    %__MODULE__{metrics: metrics, params: params, data: data, valid?: valid?}
  end

  def new(params) do
    %__MODULE__{params: params}
  end

  def
end
```

### responses.ex

The `responses.ex` will handle the final result. It will be our `Converter` in CRC.

```elixir
defmodule Endpoints.Responses do
  alias Endpoints.Requests

  defstruct [:status, :data, request: nil]

  def new(%Requests{} = request, data, status \\ :ok) do
    %__MODULE__{status: status, data: data, request: request}
  end

  def new(status, data) do
    %__MODULE__{status: status, data: data}
  end

  def ok(data \\ []) do
    new(:ok, data)
  end

  def error(data \\ []) do
    new(:error, data)
  end

  def render(%__MODULE__{} = response) do
    {response.status, response.data}
  end
end
```

### metrics.ex

The `metrics.ex` is a simple structure that can store the params and functions to send to telemetry and instrumentation services like Prometheus. It's a `façade` that can standarize and simplify those calls. This can be associated with a `Boundary` layer, because it interacts with an external component.

```elixir
defmodule Endpoints.Metrics do

  # Epoch = Start Time
  # Id = Id to Send to the metrics system
  defstruct [:epoch, :id]

  def new(id \\ 0) do
    %__MODULE__{epoch: System.monotonic_time(:microsecond), id: id}
  end

  def count(data, operation, metrics) do
    # Call A metrics system to send the count operation
    {:count, operation, data, metrics.id}
  end

  def count(operation, metrics), do: count([], operation, metrics)

  def error(data, operation, metrics) do
    {:count_error, operation, data, metrics.id}
  end

  def error(operation, metrics), do: error([], operation, metrics)

  @doc """
  Track are for time measurement since it uses the metrics's epoch
  """
  def track(data, operation, metrics) do
    {:track, operation, data, metrics.id, metrics.epoch}
  end

  def track(operation, metrics), do: track([], operation, metrics)
end
```

## Address Directory

### endpoints.ex

This is a boundary layer that will receive all the params
from the HTTP or GraphQL Request and will call the other functions and render the final response.

```elixir
defmodule Endpoints.Addresses.Endpoints do
  alias Endpoints.Addresses.Requests
  alias Endpoints.Metrics

  def get_all_addresses_for_customer_id(customer_id) do
    # initiate metrics here to have a good epoch
    Requests.get_all_addresses_for_customer_id(Metrics.new(), customer_id)
    |> Resolvers.get_all_addresses_for_customer_id()
    |> Responses.render()
  end
end
```

### metrics.ex

These are helper functions to standarize the metrics used inside all the endpoints.

May be these can be automated using [macros](https://elixir-lang.org/getting-started/meta/macros.html) or if you are adventurous a [decorator](https://hexdocs.pm/decorator/readme.html)

```elixir
defmodule Endpoints.Addresses.Metrics do
  alias Endpoints.Metrics

  def init_address_request(metrics), do: Metrics.count("INIT_ADDRESS_REQUEST", metrics)
  def init_address_request(_data, metrics), do: init_address_request(metrics)

  def count_address_found(metrics), do: Metrics.count("OK_ADDRESS_FOUND", metrics)
  def count_address_found(_data, metrics), do: count_address_found(metrics)

  def count_address_not_found(metrics), do: Metrics.count("OK_ADDRESS_NOT_FOUND", metrics)
  def count_address_not_found(_data, metrics), do: count_address_not_found(metrics)

  def track_address_get(data, metrics): Metrics.track(data, "TRACK_ADDRESS_GET", metrics)
  def track_address_get(metrics), do: track_address_get([], metrics)
end
```

### requests.ex

The address request is a `Constructor` that will standarize params and return a new `Request` struct.

Maybe it can validate the params too and see if it valid.

```elixir
defmodule Endpoints.Addresses.Requests do
  alias Endpoints.Requests

  def get_all_addresses_for_customer_id(metrics, customer_id) do
    Requests.new(metrics, %{customer: %{id: customer_id}})
  end

end
```

### resolvers.ex

The resolver is the one who orchestates queries, requests and responses.

```elixir
defmodule Endpoints.Addresses.Resolvers do
  alias Endpoint.Responses
  alias Endpoints.Addresses.Requests
  alias Endpoints.Addresses.Repo.Queries
  alias Endpoints.Addresses.Metrics

  def get_all_addresses_for_customer_id(%Requests{} = request) do
    metrics = request.metrics
    Metrics.init_address_request(metrics)
    case Queries.addresses(customer: request.params.customer.id) do
      [] ->
        Metrics.count_address_not_found(metrics)
        |> Metrics.track_address_get(metrics)
        Responses.ok()
      addresses ->
        Metrics.count_address_found(metrics)
        Metrics.track_address_get(addresses, metrics)
        Responses.ok(addresses)
    end
  end
end
```

### Repo Directory

These are two files that are using CQRS to store queries.

{% embed https://dev.to/clsource/using-cqrs-in-phoenix-contexts-51oa %}

#### queries.ex

```elixir
defmodule Endpoints.Addresses.Repo.Queries do
  import Ecto.Query
  use Ecto.Repo

  def addresses(customer: id) do
    from(a in Address,
      where: a.customer_id == ^id
    )
    |> Repo.all()
  end
end
```

#### commands.ex

```elixir
defmodule Endpoints.Addresses.Repo.Commands do
end
```

## Conclusion

These are just some thoughts about code organization. I tried to follow _CRC_ and apply different layers of code organization. 


Thanks.