lib/domainex.ex

# SPDX-License-Identifier: MIT
defmodule Domainex do
  @moduledoc """
  A `domainex` is an Elixir library which provides:
  - common elixir typespec models
  - common domain typespec models
  - a helper for functional dependencies
  - a helper for common domain value objects
  - a helper for common domain needs
  """
  alias Domainex.Aggregate
  alias Domainex.Event

  @typep error_type :: String.t() | atom()

  @typep error_payload ::
    String.t()
    | struct()
    | map()

  @typedoc """
  Common elixir error type spec actually looks like:

  ```elixir
    {:error, "error message"}
  ```

  I'm agree with the structure, but I think we need more detail and explicit
  for the error type. What kind of errors? Is it internal? Is it from core domain?
  Is it from application's level? Or is it an exception?

  The `error_type()` has two possible types:
  - String.t()
  - atom()

  By providing the `error_type()` we can provides more explicit information about
  an error, and help us to provide better error handling based on the error's types

  For the `error_payload()` , it has three possible types
  - String.t()
  - struct()
  - map()

  It is possible to just using a simple string for our error message, but I think sometimes
  we also need to provide rich error informations, maybe by providing some structs or a map,
  which contains specific application or business error handling, such as for application status
  codes with their definition/message.
  """
  @type error :: {:error, {error_type(), error_payload()}}

  @typedoc """
  `success()` actually is a common Elixir's convention result for the success return values. It just
  follow common convention

  ```elixir
    {:ok, any()}
  ```
  """
  @type success :: {:ok, any()}

  @typedoc """
  A `result()` actually follow Rust's convention from their `Result<T, E>`. A `result()`
  is a return value which only has two possibilities a `success()` or `error()` values.

  It's common in Elixir's function which provides two possible return values and looks like this

  ```elixir
  @spec do_something(a :: atom()) :: {:ok, any()} | {:error, any()}
  def do_something(a) do
  end
  ```

  To simplify the function's signature, I make it to:

  ```elixir
  @spec do_something(a :: atom()) :: Domainex.result()
  def do_something(a) do
  end
  ```
  """
  @type result :: success() | error()

  @typedoc """
  An `event_name()` used to give a name to some event
  """
  @type event_name :: atom()

  @typedoc """
  An `event_payload()` used for payload for some event, it can
  be a `struct()` or `map()`.
  """
  @type event_payload :: struct() | map()

  @typedoc """
  An `event()` will used at aggregate. An aggregate will trigger / emit
  an event for each domain activity already processed.
  """
  @type event :: {:event, Event.Structure.t()}

  @typedoc """
  An `aggregate_name` used to define the business needs behind an `aggregate`.
  """
  @type aggregate_name :: String.t() | atom()

  @typedoc """
  An `aggregate_payload()` should be a
  """
  @type aggregate_payload :: struct() | map()

  @typedoc """
  An `aggregate` in DDD actually is a cluster of *objects*, it possible to be a single entity, or a group
  of entities. The definition of *object* here is just a simple `struct()`.

  This type of `aggregate()`, already set the `aggregate_payload()` to using `DomainEx.Aggregate.Structure`
  as main data structure.
  """
  @type aggregate :: {:aggregate, Aggregate.Structure.t()}
end