【Go】環境毎の設定値運用戦略

# はじめに
こんにちは！mizukoです！
先日[PaPut](https://paput.io)という個人開発のサービスをβ版としてリリースしました！

その際、今後の個人開発ライフのために、基盤作りを行ったのですが、
環境毎の設定値運用の戦略も検討したので、アウトプットしていきたいと思います！

PaPutについてはぜひ[こちら](https://qiita.com/mizuko_dev/items/5335f93bb3c93f407796)をご覧いただけますと幸いです🤗

# 戦略
環境毎の設定値を運用するにあたり、以下方針で運用していきます。
- .envやtomlを使わず、環境変数を利用する
- ローカル開発では.envrcを使う
- 本番環境では、ホスティングサービスやIaaSで環境変数を扱えるサービスを選ぶ
- デプロイはGithubと連携し自動で行う

# ソースコード
```go
package config

import (
	"os"
	"strconv"
)

type DatabaseConfig struct {
	Host     string
	Name     string
	User     string
	Password string
	Port     string
}

type GoogleConfig struct {
	ClientID     string
	ClientSecret string
	RedirectURL  string
}

type JwtConfig struct {
	Secret              string
	AccessTokenDuration int
}

type Config struct {
	Database DatabaseConfig
	Google   GoogleConfig
	Jwt      JwtConfig
}

func LoadConfig() (*Config, error) {
	config := &Config{
		Database: DatabaseConfig{
			Host:     getEnv("DB_HOST", ""),
			Name:     getEnv("DB_NAME", ""),
			User:     getEnv("DB_USER", ""),
			Password: getEnv("DB_PASSWORD", ""),
			Port:     getEnv("DB_PORT", ""),
		},
		Google: GoogleConfig{
			ClientID:     getEnv("GOOGLE_CLIENT_ID", ""),
			ClientSecret: getEnv("GOOGLE_CLIENT_SECRET", ""),
			RedirectURL:  getEnv("GOOGLE_REDIRECT_URL", ""),
		},
		Jwt: JwtConfig{
			Secret:              getEnv("JWT_SECRET", ""),
			AccessTokenDuration: getEnvAsInt("JWT_ACCESS_TOKEN_DURATION", 0),
		},
	}

	return config, nil
}

func getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}

	return fallback
}

func getEnvAsInt(key string, fallback int) int {
	strValue := getEnv(key, "")
	if value, err := strconv.Atoi(strValue); err == nil {
		return value
	}

	return fallback
}
```

os.LookupEnvを利用し、環境変数から値を取得しています。
さらにos.LookupEnvをラップし、値が存在しない場合のデフォルト値を設定できる様にしています。

後は利用したところで、以下の様にすればOKです。簡単ですね👍
```go
config.LoadConfig()
```

# ローカル開発環境
.envrcとdirenvを使い、プロジェクト毎に環境変数を読み込むように設定します。
## direnvの設定
direnv をインストール

```
brew install direnv
```

.zshrc に以下追加

```
eval "$(direnv hook zsh)"
```

.zshrc の読み込み

```
source .zshrc
```

## .envrcを作成
プロジェクトルートに.envrcを作成します。
```env
export DB_HOST=hoge
export DB_PORT=5432
export DB_NAME=hoge
export DB_USER=hoge
export DB_PASSWORD=password

~ 略 ~
```

## .envrcの適用
プロジェクトルートで以下を実行します。
```
direnv allow
```

## compose.yml
compose.ymlで環境変数を読み込みます。
```yml
~ 略 ~

environment:
    DB_HOST: $DB_HOST
    DB_PORT: $DB_PORT
    DB_NAME: $DB_NAME
    DB_USER: $DB_USER

~ 略 ~
```

# 本番環境
本番環境ではホスティングサービスやIaaS上で環境変数を設定します。
以下はRenderのBlueprintの設定例です。

```yml
~ 略 ~

envVars:
    - key: PORT
    value: 8080
    - key: GIN_MODE
    value: release
    - key: DB_HOST
    fromDatabase:
        name: hoge
        property: host
    - key: DB_NAME
    fromDatabase:
        name: hoge
        property: database
    - key: DB_USER
    fromDatabase:
        name: hoge
        property: user
    - key: DB_PASSWORD
    fromDatabase:
        name: hoge
        property: password
    - key: DB_PORT
    fromDatabase:
        name: hoge
        property: port

~ 略 ~
```

# おわりに
Goにおける、環境毎の設定値運用戦略を記載してみました！
.envやtomlの運用も考えましたが、結局自動デプロイと相性が良いシンプルな環境変数の運用にしました。
IaCを前提とした開発なら、より管理しやすくなると思います...！

環境毎の設定値の管理に迷っている方に参考なれば幸いです🫶

冒頭でも記載しましたが、[PaPut](https://paput.io)というサービスのβ版をリリースしました！
是非手軽に使って、フィードバックいただけますと嬉しいです！
PaPutについてはぜひ[こちら](https://qiita.com/mizuko_dev/items/5335f93bb3c93f407796)もご覧ください！