【Server Side Swift】Vaporで作るアプリケーションの設定ファイルについて【Vapor】

# Vaporとは

Swift製のWeb Frameworkです。
macOS, Ubuntuで動くサーバーサイドのプログラムをSwiftで簡単に書くことができます。

http://vapor.codes/

# Vaporで作るアプリケーションの設定

今回は、このVaporのアプリケーションの設定値の設定方法を見ていきたいと思います。

Vaporではアプリケーションの設定を次の２通りの方法で指定することができます。

1. jsonの設定ファイルに設定を記述
2. コマンドライン起動時のオプションで指定

これら２通りの方法について説明します。

## 1. jsonの設定ファイルに設定を記述

ドキュメントはこちらをご覧ください。
https://vapor.github.io/documentation/guide/config.html#quickstart

任意の名前のjsonファイルに任意の設定値を記述し、アプリケーションコードから使用することができます。
ただし、それにはルールと使い方があるので、それらをみていきましょう。

### ▶︎ 設定ファイルの置き場所

設定ファイルはプロジェクトの **`Config/`** ディレクトリ下に置いていきます。


```bash:例
WorkingDirectory/
├── Config/
│   ├── server.json
```

### ▶︎ 設定ファイルの書き方

設定ファイルは単純にjsonファイルなので、例えば次のように書きます。

```json:server.json（例）
{
  "http": {
    "host": "0.0.0.0",
    "port": 8080
  }
}
```

### ▶︎ アプリケーションコードでの設定値の使い方

```json:settings.json（例）
{
  "string_value": "hoge",
  "int_value": 123,
  "array_value": [
    "fuga",
    "piyo"
  ],
  "nest": {
    "nested_value": "nested"
  }
}
```

上記のようなjsonファイルで設定した値をコード内で使うには以下のような書式になります。

```swift:server.jsonのhttp->hostの値を取る例
let drop = Droplet()

// drop.config[fileName, path, to, key...]
drop.config["settings", "string_value"]?.string         // Optional("hoge")
drop.config["settings", "int_value"]?.int               // Optional(123)
drop.config["settings", "array_value", 1]?.string       // Optional("piyo")
drop.config["settings", "nest", "nested_value"]?.string // Optional("nested")
```

`config`のサブスクリプトの１つ目が対象のファイル名、２つ目以降がキーの名前になっています。
設定ファイルがjsonなので、キーがネストされている場合には、２つ目以降のキーの名前をネストされたキーを辿って記述することで最終的な値を得ることができます。

```swift:前述のserver.jsonからhostの値を取る例
// drop.config[fileName, path, to, key...]
drop.config["server", "http", "host"]?.string // Optional("0.0.0.0")
```

#### - ネストされた設定値の応用編

次のような形で、`pathIndexableObject`を使用して`[String : Config]`型で受け取ることで、ネストされた設定値を変数を介して後から取得することもできます。

```swift
let serverSettings = drop.config["server", "http"]?.pathIndexableObject
let host = serverSettings?["host"]?.string // Optional("0.0.0.0")
let port = serverSettings?["port"]?.int // Optional(8080)
```


### ▶︎ 環境に合わせた設定値

設定ファイルは `Config/` 下に置くと書きましたが、 `Config/` 下にもさらにディレクトリを分けて環境ごとに設定値を変えることができます。

`Config/`下には通常、以下の３つのディレクトリが想定されています。

- **`Config/production`**
    - production環境の時にこのディレクトリ内の設定ファイルが優先的に読まれます。
- **`Config/development`**
    - development環境の時にこのディレクトリ内の設定ファイルが優先的に読まれます。
- **`Config/secrets`**
    - どの環境においても最優先でこのディレクトリ内の設定ファイルが読まれます。
    - このディレクトリ内はデフォルトでgit管理下から外されています。（`.gitignore`の対象）

使い方としては、

1. `Config/`直下の設定ファイルでデフォルト値を設定
2. 各環境にて上書きしたい設定に関しては、環境ごとのディレクトリ内にも同じファイル名・同じ構造の設定ファイルを設置
3. ローカル環境で最優先で上書きしたい設定を`Config/secrets/`以下に同じファイル名・同じ構造で設置

という感じになるかと思います。

これらの設定は任意ですが、
こういった優先度のルールを知っておくことで、設定の変更が柔軟に行えるかと思います。


```bash
# Config/secrets/server.json が読まれる

WorkingDirectory/
├── Config/
│   ├── server.json
│   ├── production/
│   │   └── server.json
│   ├── development/
│   │   └── server.json
│   └── secrets/
│       └── server.json
```

```bash
# development環境では Config/development/server.json 、
# production環境では Config/production/server.json が読まれる

WorkingDirectory/
├── Config/
│   ├── server.json
│   ├── production/
│   │   └── server.json
│   ├── development/
│   │   └── server.json
│   └── secrets/
```

```bash
# development環境では Config/server.json、
# production環境では Config/production/server.json が読まれる

WorkingDirectory/
├── Config/
│   ├── server.json
│   ├── production/
│   │   └── server.json
│   ├── development/
│   └── secrets/
```

## 2. コマンドライン起動時のオプションで指定

ドキュメントはこちらをご参照ください。
https://vapor.github.io/documentation/guide/config.html#command-line

### ▶︎ 優先度
コマンドラインでの指定は、前述のjsonファイルの設定よりも適応の優先度が高いので、設定ファイルの設定を無視してこちらの設定が適応されます。

### ▶︎ 書き方と使い方

先ほどの例と同じく、次のような設定ファイルを作っているとします。

```json:settings.json（例）
{
  "string_value": "hoge",
  "int_value": 123,
  "array_value": [
    "fuga",
    "piyo"
  ],
  "nest": {
    "nested_value": "nested"
  }
}
```

#### 書式
 **`--CONFIG:FILE-NAME.KEY=CUSTOM-VALUE`**

#### 書き方の例

```bash
vapor run --config:settings.string_value=foo
```

#### 使い方

使い方は前述の方法と同じです。

```swift
drop.config["setting", "string_value"]?.string // Optional("foo")
```

前述の通り、設定ファイルの設定よりもコマンドラインでの設定の方が優先度が高いので、`foo`という値に上書きされます。

# その他補足

## ▶︎ 設定値を変えた時のアプリケーションのビルドについて

- jsonファイルの設定値を変更してもアプリケーションを再度ビルドし直す必要はない。
- また、アプリケーションの実行中にjsonファイルを変更しても、設定値の読み込み処理さえ走れば変更した値が読まれる

## ▶︎ Dropletを使わない場合

これまでの例では、`Droplet`クラスを使用して、Dropletを介して設定値を取得していました。
通常はそのような形で取得すればいいかと思いますが、Dropletを使わない方法も紹介します。

```swift
#if os(Linux)
let workingDirectory = "./"
#else
let workingDirectory: String = {
    let parent = #file.characters.split(separator: "/").map(String.init).dropLast().joined(separator: "/")
    let path = "/\(parent)/../"
    return path
}()
#endif

let config = try Config(prioritized: [.directory(root: workingDirectory + "Config/")])
config["server", "http", "host"]?.string // Optional("0.0.0.0")
```

こちらは、Vaporのサンプルコードの一つの[Slack botのコード](https://github.com/vapor/slack-bot)にて書かれている書き方になります。
`Config`構造体を使っています。
DropletもこのConfig構造体をプロパティーとして持っているだけなので、やっていること自体は同じですね。

## ▶︎ 設定値を取得する別の書き方

これまで紹介した書き方は次のようなものでした。

```swift:これまでの書き方
drop.config["server", "http", "host"]?.string
```

前述の記述以外に下のような書き方もできます。

### 設定値のキーまでのパスをarrayで渡す

```swift
let pathIndex = ["server", "http", "host"]
drop.config[pathIndex]?.string
```

まあ、この書き方はあまりメリット無いかもしれないですね。

### ドット区切りの文字列でキーまでのパスを指定する
```swift
drop.config[path: "server.http.host"]?.string
```

これらは、**`PathIndexable`**というパッケージとしてVaporの中に組み込まれているので、実装はこちらのリポジトリをご確認ください。
https://github.com/vapor/path-indexable



# ドキュメント

https://vapor.github.io/documentation/guide/config.html