ElixirでIDを生成したい



[銀の弾丸]: https://ja.wikipedia.org/wiki/%E9%8A%80%E3%81%AE%E5%BC%BE%E4%B8%B8
[Elixir]: https://elixir-lang.org/
[Erlang]: https://www.erlang.org/
[Phoenix]: https://www.phoenixframework.org/
[Nerves]: https://hexdocs.pm/nerves
[Livebook]: https://livebook.dev/
[IEx]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[ID]: https://ja.wikipedia.org/wiki/ID
[Phoenix.Token]: https://hexdocs.pm/phoenix/Phoenix.Token.html
[mix phx.gen.auth]: https://hexdocs.pm/phoenix/mix_phx_gen_auth.html
[Mix.Tasks.Phx.Gen.Secret]: https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html
[オペレーティングシステム]: https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0
[DateTime.to_iso8601/3]: https://hexdocs.pm/elixir/DateTime.html#to_iso8601/3
[DateTime.to_unix/2]: https://hexdocs.pm/elixir/DateTime.html#to_unix/2
[System.os_time/1]: https://hexdocs.pm/elixir/System.html#os_time/1
[System.monotonic_time/1]: https://hexdocs.pm/elixir/System.html#monotonic_time/1
[MD5]: https://ja.wikipedia.org/wiki/MD5
[:crypto.strong_rand_bytes/1]: https://www.erlang.org/doc/man/crypto.html#strong_rand_bytes-1
[Kernel.binary_slice/2]: https://hexdocs.pm/elixir/Kernel.html#binary_slice/2
[Kernel.binary_part/3]: https://hexdocs.pm/elixir/Kernel.html#binary_part/3
[zyro/elixir-uuid]: https://hexdocs.pm/uuid/readme.html
[UUID]: https://ja.wikipedia.org/wiki/UUID

さまざまな場面で[ID]を生成したくなることがあると思います。Qiitaの記事のURLも何らかのアルゴリズムでIDが生成されています。例、[https://qiita.com/mnishiguchi/items/90380b61df4e41966556](https://qiita.com/mnishiguchi/items/90380b61df4e41966556)

[ID]生成のやり方と言っても要求事項によるので[銀の弾丸]はないと思います。

いろんなパターンを知っておくといざという時の判断材料になると思い、お手本があればメモするようにしています。

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e

## 日付・時間

- 単純なデータなので処理が高速
- 厳密にいうとIDではないがこれで用が足りることもある

### [System.monotonic_time/1]

- 不特定の時点から始まる単調に増加する時間
- 例、`50549`

```elixir
System.monotonic_time(:millisecond)
```

https://hexdocs.pm/elixir/System.html#monotonic_time/1

### [System.os_time/1]

- [オペレーティングシステム](OS)の時間
- 例、`1668560784473`

```elixir
System.os_time(:millisecond)
```

https://hexdocs.pm/elixir/System.html#os_time/1

### [DateTime.to_unix/2]

- `System.os_time(:second)`と同じっぽい
- 例、`1668560794`

```elixir
DateTime.utc_now |> DateTime.to_unix
```

https://hexdocs.pm/elixir/DateTime.html#to_unix/2

### [DateTime.to_iso8601/3]

- 例、`"2022-11-16T01:06:51.322281Z"`

```elixir
DateTime.utc_now |> DateTime.to_iso8601
```

https://hexdocs.pm/elixir/DateTime.html#to_iso8601/3

## 文字列のMD5ハッシュ値を計算しBase16にエンコード

- 不可解な値に見せたいときに最適
- 例、`"E3FABC6EBCAA52300311DF162AE35BF1"`

```elixir:IEx
iex> "闘魂" |> :erlang.md5() |> Base.encode16()
"E3FABC6EBCAA52300311DF162AE35BF1"
```

```elixir:IEx
iex> "闘魂" |> :erlang.md5() |> Base.encode16(case: :lower)
"e3fabc6ebcaa52300311df162ae35bf1"
```

https://www.erlang.org/doc/man/crypto.html

https://www.erlang.org/doc/man/erlang.html#md5-1

https://hexdocs.pm/elixir/Base.html#encode16/2

## [:crypto.strong_rand_bytes/1]

- 衝突を避けたいときに最適
- [Livebook]でランダムIDの生成に使用されている
- [Phoenix]の[Mix.Tasks.Phx.Gen.Secret]で使用されている

### [Livebook]の例

```elixir
# random_short_id
# 例、"kmrfwizk"
:crypto.strong_rand_bytes(5) |> Base.encode32(case: :lower)
```

```elixir
# random_id
# 例、"3nbztnwq5wi6fhmerpgxbwbryo6b27os"
:crypto.strong_rand_bytes(20) |> Base.encode32(case: :lower)
```

```elixir
# random_cookie
# 例、"5QpirBHr0XWdCCdudCsvZ-nuNLuiRAkF0wR_DdKm03plKbwEvvSG"
:crypto.strong_rand_bytes(39) |> Base.url_encode64()
```

https://github.com/livebook-dev/livebook/blob/42d2386a2d4d3d115946fc6d9a98f2df6319f7a4/lib/livebook/utils.ex

文字列の長さを一定にしたい場合は、[Kernel.binary_slice/2] (または [Kernel.binary_part/3])が便利そうです。

```elixir
:crypto.strong_rand_bytes(20) |> Base.url_encode64 |> binary_part(0, 20)
```

```elixir
:crypto.strong_rand_bytes(20) |> Base.url_encode64 |> binary_slice(0..19)
```

https://www.erlang.org/doc/man/crypto.html#strong_rand_bytes-1

https://hexdocs.pm/elixir/Kernel.html#binary_slice/2

https://hexdocs.pm/elixir/Kernel.html#binary_part/3

### [Mix.Tasks.Phx.Gen.Secret]の例

- ランダム文字列の長さの最小値が32となっている
- [Kernel.binary_part/3]を用いて結果をユーザー指定の長さに調整している

```elixir
defmodule MySecret do
  def random_string(length) when length > 31 do
    :crypto.strong_rand_bytes(length)
    |> Base.encode64(padding: false)
    |> binary_part(0, length)
  end

  def random_string(_) do
    Mix.raise "The secret should be at least 32 characters long"
  end
end

MySecret.random_string(32)
```

https://github.com/phoenixframework/phoenix/blob/bd275cf741163e3fca0d928606a6ed775dd33cf4/lib/mix/tasks/phx.gen.secret.ex

## [UUID]

- 同一の文字列が2つ生成される可能性が高い大規模なコンテキストに最適
- [zyro/elixir-uuid]パッケージをインストールする必要がある
- 例、`69134ff3-4bfe-3945-a04d-7e694c77121c`

### [UUID]のバージョン

- バージョン1 - MAC address + datetime into 128 bits
- バージョン3 - MD5 hash into 128 bits
- バージョン4 - random data into 128 bits
- バージョン5 - SHA1 hash into 128 bits

迷ったらとりあえずバージョン3を選択すると無難そう。

[zyro/elixir-uuid]パッケージをインストールします。

```elixir:IEx
iex> Mix.install [{:elixir_uuid, "~> 1.2.0"}]
```

名前空間を指定する必要がある。迷ったら`:dns`にしておけばOK。

```elixir:IEx
iex> UUID.uuid3(:dns, "mnishiguhchi.com")
"0aaa56b1-0810-3a73-aa3d-63a2f6b3d28d"
```

有効なUUIDを名前空間として使用することも可能。

```elixir:IEx
iex> UUID.uuid3("0aaa56b1-0810-3a73-aa3d-63a2f6b3d28d", "闘魂")
"16c19ad5-e030-3829-9599-26961d8ef802"
```

https://medium.com/elixir-labs/creating-uniques-ids-on-elixir-6f705688df06

## ご参考までに

https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf