# `DDD.Identity`
[🔗](https://github.com/blackuprise/vicander/blob/v0.1.1/lib/ddd/model/identity.ex#L1)

Identities of entities and aggregates, and generating them.

## The identity string

An entity's identity is made of one or more fields, declared with one `identity/2` each:

    identity(:tenant_id, :uuid)
    identity(:sku, :string)

`Entity.identity(entity)` returns it as a string: the value itself for a single field, the
values joined with `:` for several (`"6f1f...:A-100"`). A `\` or `:` inside a part is escaped
(`\\`, `\:`) so the string always splits back into the same parts (`decode/1`). The string
is what `find_by_id/1`, the outbox `aggregate_id` and a `{:ref, Aggregate}` to a composite
aggregate use.

`Entity.build_identity(parts)` makes the same string from a map or keyword list of the parts,
without an entity. `Entity.identity_fields()` lists the part names.

A composite identity part must be a `:string`, `:uuid`, `:integer`, `:atom`, `:date` or
enum field. Every part is required, and none may change once the entity exists.

## Generating ids

`new/1` fills in a UUIDv7 only for a single `identity(:id, :uuid)` that the caller left out.
For any other identity the caller passes every part. `generate/0` and the functions below
make ids by hand:

  * `uuid7/0` (the default, `generate/0`) - time-ordered: ids created later sort later, which
    keeps btree indexes compact
  * `uuid4/0` - random
  * `uuid1/0`, `uuid3/2`, `uuid5/2` - time and name based (see `UUID`)

# `build`

```elixir
@spec build(module(), map() | keyword()) :: String.t()
```

Build the identity string for `module` from a map or keyword list holding its parts. Raises
`ArgumentError` when a part is missing or `nil`.

# `decode`

```elixir
@spec decode(String.t()) :: [String.t()]
```

Split a composite identity string back into its parts (the inverse of `encode/1`), also when a
part itself contains a `:`.

    iex> DDD.Identity.decode("t1:sku-9")
    ["t1", "sku-9"]
    iex> ["a:b", "c"] |> DDD.Identity.encode() |> DDD.Identity.decode()
    ["a:b", "c"]

# `encode`

```elixir
@spec encode([term()]) :: String.t()
```

Join identity parts into the identity string. One part is returned as text unchanged; several
are escaped and joined with `:`.

    iex> DDD.Identity.encode(["order-7"])
    "order-7"
    iex> DDD.Identity.encode(["t1", "sku-9"])
    "t1:sku-9"

# `generate`

```elixir
@spec generate() :: String.t()
```

A new id: a UUIDv7.

# `id_of`

```elixir
@spec id_of(term()) :: String.t() | nil
```

The identity string of an entity instance, for naming it in an error, or `nil` when it has
none or can't be told (a part is missing or has no text form). Never raises.

# `key`

```elixir
@spec key(struct()) :: term()
```

The value a repository stores an entity under: the field value for a single-field identity,
the identity string for a composite one. `nil` for a struct without identity.

# `put_generated`

```elixir
@spec put_generated(map(), [{atom(), atom(), keyword()}]) :: map()
```

Fills in a generated `:id` when the identity is a lone `identity(:id, :uuid)` and the caller
left it out. Any other identity is returned untouched. A function rather than inline code in
the generated `new/1`, so a value object (no identity) does not get a clause that can never
match.

# `uuid1`

```elixir
@spec uuid1() :: String.t()
```

A UUIDv1 (time and node based).

# `uuid3`

```elixir
@spec uuid3(atom() | String.t(), String.t()) :: String.t()
```

A name-based UUIDv3 (MD5). `namespace` is `:dns`, `:url`, `:oid`, `:x500` or a UUID.

# `uuid4`

```elixir
@spec uuid4() :: String.t()
```

A random UUIDv4.

# `uuid5`

```elixir
@spec uuid5(atom() | String.t(), String.t()) :: String.t()
```

A name-based UUIDv5 (SHA-1). `namespace` is `:dns`, `:url`, `:oid`, `:x500` or a UUID.

# `uuid7`

```elixir
@spec uuid7() :: String.t()
```

A UUIDv7: a 48-bit millisecond timestamp followed by random bits.

Ids made in different milliseconds sort in creation order. Ids made within the same
millisecond are unique but not ordered.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
