ElixirでPAY.JPのライブラリ作成シリーズ⑤ config.exの作成

https://qiita.com/Yoosuke/private/5c94b892e053865be041

こちらの続きです。

```lib/payjpex/config.ex
defmodule Payjpex.Config do
  @moduledoc """
  PAY.JP APIの設定を管理するモジュール。

  このモジュールはGenServerとして実装され、以下の機能を提供します：
  - API設定の保存と取得
  - 設定値の動的な更新
  - デフォルト値の管理
  - 設定のリセット機能

  ## 設定可能な項目

    * `api_key` - PAY.JPのAPIキー
    * `api_base` - APIのベースURL
    * `api_version` - 使用するAPIバージョン
    * `open_timeout` - 接続タイムアウト時間（ミリ秒）
    * `read_timeout` - 読み取りタイムアウト時間（ミリ秒）
    * `max_retry` - リトライ最大回数
    * `retry_initial_delay` - 初回リトライまでの待機時間（ミリ秒）
    * `retry_max_delay` - 最大リトライ待機時間（ミリ秒）

  ## Examples
      # 設定の取得
      api_key = Payjpex.Config.get(:api_key)

      # 設定の更新
      Payjpex.Config.put(:api_key, "sk_test_xxxxx")

      # 全設定の取得
      all_config = Payjpex.Config.get_all()
  """

  use GenServer

  @doc """
  設定管理用GenServerを起動します。

  アプリケーション起動時にスーパーバイザーによって自動的に呼び出されます。
  プロセスは名前付きプロセスとして登録され、アプリケーション全体で単一のインスタンスとして動作します。

  ## パラメータ
    * `_opts` - 起動オプション（現在は使用していません）

  ## 戻り値
    * `{:ok, pid}` - プロセスが正常に起動した場合
    * `{:error, term}` - 起動に失敗した場合
  """
  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @doc """
  指定されたキーの設定値を取得します。

  ## パラメータ
    * `key` - 取得したい設定のキー

  ## 戻り値
    設定値。キーが存在しない場合はnil

  ## Examples
      iex> Payjpex.Config.get(:api_base)
      "https://api.pay.jp"
  """
  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end

  @doc """
  指定されたキーに値を設定します。

  ## パラメータ
    * `key` - 設定するキー
    * `value` - 設定する値

  ## 戻り値
    * `:ok` - 設定の更新に成功した場合

  ## Examples
      iex> Payjpex.Config.put(:api_key, "sk_test_xxxxx")
      :ok
  """
  def put(key, value) do
    GenServer.call(__MODULE__, {:put, key, value})
  end

  @doc """
  全ての設定値をマップとして取得します。

  ## 戻り値
    現在の全設定値を含むマップ

  ## Examples
      iex> Payjpex.Config.get_all()
      %{
        api_key: nil,
        api_base: "https://api.pay.jp",
        api_version: nil,
        open_timeout: 30_000,
        read_timeout: 90_000,
        max_retry: 0,
        retry_initial_delay: 2_000,
        retry_max_delay: 32_000
      }
  """
  def get_all do
    GenServer.call(__MODULE__, :get_all)
  end

  @doc """
  全ての設定をデフォルト値にリセットします。

  ## 戻り値
    * `:ok` - リセットに成功した場合

  ## Examples
      iex> Payjpex.Config.reset()
      :ok
  """
  def reset do
    GenServer.call(__MODULE__, :reset)
  end

  # サーバーコールバック

  @doc false # プライベートコールバック
  @impl true
  def init(:ok) do
    initial_state = %{
      api_key: nil,
      api_base: Application.get_env(:payjpex, :api_base, "https://api.pay.jp"),
      api_version: Application.get_env(:payjpex, :api_version, nil),
      open_timeout: Application.get_env(:payjpex, :open_timeout, 30_000),
      read_timeout: Application.get_env(:payjpex, :read_timeout, 90_000),
      max_retry: Application.get_env(:payjpex, :max_retry, 0),
      retry_initial_delay: Application.get_env(:payjpex, :retry_initial_delay, 2_000),
      retry_max_delay: Application.get_env(:payjpex, :retry_max_delay, 32_000)
    }
    {:ok, initial_state}
  end

  @doc false # プライベートコールバック
  @impl true
  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  @doc false # プライベートコールバック
  @impl true
  def handle_call({:put, key, value}, _from, state) do
    {:reply, :ok, Map.put(state, key, value)}
  end

  @doc false # プライベートコールバック
  @impl true
  def handle_call(:get_all, _from, state) do
    {:reply, state, state}
  end

  @doc false # プライベートコールバック
  @impl true
  def handle_call(:reset, _from, _state) do
    new_state = init(:ok) |> elem(1)
    {:reply, :ok, new_state}
  end
end
```

では、テストも書いていきましょう。

```test/config_test.exs
defmodule Payjpex.ConfigTest do
  use ExUnit.Case
  doctest Payjpex.Config

  setup do
    # 各テストの前に設定をリセット
    Payjpex.Config.reset()
    :ok
  end

  describe "設定の管理" do
    test "デフォルト値が正しく設定されていること" do
      assert Payjpex.Config.get(:api_base) == "https://api.pay.jp"
      assert Payjpex.Config.get(:api_key) == nil
      assert Payjpex.Config.get(:open_timeout) == 30_000
    end

    test "値を更新できること" do
      Payjpex.Config.put(:api_key, "sk_test_123")
      assert Payjpex.Config.get(:api_key) == "sk_test_123"
    end

    test "全ての設定を取得できること" do
      config = Payjpex.Config.get_all()
      assert is_map(config)
      assert Map.has_key?(config, :api_base)
      assert Map.has_key?(config, :api_key)
    end

    test "設定をリセットできること" do
      Payjpex.Config.put(:api_key, "sk_test_123")
      Payjpex.Config.reset()
      assert Payjpex.Config.get(:api_key) == nil
    end
  end
end
```

さて、この辺りでそろそろ、テスト実行して動作確認したいですが、Apiclientも作っておく必要があるので、中身の無い簡易的なものを作成しておきます。

```lib/payjpex/api_client.ex
defmodule Payjpex.ApiClient do
  @moduledoc """
  PAY.JP APIとの通信を処理するモジュール。
  """
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def init(:ok) do
    {:ok, %{}}
  end
end
```

ApiClientはまた後ほどじっくり作っていくとして、まずはここまでの実装をテストできるように準備します。テストコードは書いてあるのであとは設定を追加するだけです。

ルートディレクトリにconfigフォルダを追加して、test用の設定ファイルを追加します。

```config/test.exs

import Config

config :payjpex,
  api_key: "sk_test_123",
  api_base: "https://api.pay.jp",
  api_version: nil,
  open_timeout: 30_000,
  read_timeout: 90_000
```

続いて、applicationに mod: の設定を追加します。

```mix.exs

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {Payjpex, []}  # アプリケーションのエントリーポイントを指定
    ]
  end

```

```mod: {Payjpex, []}``` これは、なぜ追加するかというと、```payjpex.ex```で、```use Application```を利用している為、アプリケーション起動時に自動的にスーパーバイザーが起動し、設定された子プロセスが自動的に開始して、アプリケーションのライフサイクル管理にするためで、自動的にstart関数を呼び出すようにする為です。

```lib/payjpex.ex
defmodule Payjpex do
  use Application  # これと関連している

  def start(_type, _args) do  # この関数が自動的に呼ばれる
    children = [
      {Payjpex.Config, []},
      {Payjpex.ApiClient, []}
    ]
    Supervisor.start_link(children, opts)
  end
end
```

つまり、この設定により、アプリケーションとして正しく動作するための基本的な構造が確立されます。