この章で行うこと
- 環境変数を管理する
Config構造体を実装する mainパッケージで使用する
環境変数
第7章では、インフラストラクチャ層で MySQL と Redis に接続します。MySQL と Redis、およびアプリケーションサーバーはすべて Docker コンテナ内で「起動」している状態です。しかし、アプリケーションがそれらに「接続」するには、Docker Compose ファイル (compose.yml) に定義された環境変数を仕様する必要があります。
以下はappコンテナのenvironment部分のみを抜粋したものです。
environment:
PORT: ":8080"
TODO_DB_HOST: db
TODO_DB_PORT: 3306
TODO_DB_USER: user
TODO_DB_PASSWORD: pswd
TODO_DB_NAME: todo-db
TODO_KVS_HOST: kvs
TODO_KVS_PORT: 6379
これらをアプリケーション内で効率的に管理するためには、サードパーティのパッケージを使うと便利です。今回は以下のパッケージを使用します。
以下のコマンドを打って、インストールしておいてください。
make get-app name="github.com/kelseyhightower/envconfig"
実装する
Configの実装・テスト
早速実装に取り掛かりましょう。appディレクトリに/configディレクトリを作成してください。
// app/config/config.go
package config
import "github.com/kelseyhightower/envconfig"
type Config struct {
Server server
MySQL mysql
Redis redis
}
type server struct {
Port string `envconfig:"PORT"`
}
type mysql struct {
Name string `envconfig:"TODO_DB_NAME"`
User string `envconfig:"TODO_DB_USER"`
Password string `envconfig:"TODO_DB_PASSWORD"`
Port string `envconfig:"TODO_DB_PORT"`
Host string `envconfig:"TODO_DB_HOST"`
}
type redis struct {
Host string `envconfig:"TODO_KVS_HOST"`
Port string `envconfig:"TODO_KVS_PORT"`
}
func NewConfig() (*Config, error) {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
Config構造体のみ公開しており、server、mysql、redis構造体を埋め込んでいます。envconfigというタグをフィールドに付加することで、設定している環境変数を読み込み、構造体にマッピングしてくれます。
NewConfig()関数はConfig構造体を返すものです。
きちんと環境変数を読み込めているかを確かめるテストも書いておきます。
// app/config/config_test.go
package config
import "testing"
func TestInitConfig(t *testing.T) {
tests := []struct {
name string
want string
setEnv string
wantErr bool
}{
{
name: "正常系ーADDRESS環境変数を読み込める",
want: ":8081",
setEnv: ":8081",
wantErr: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Setenv("PORT", tt.setEnv)
cfg, err := NewConfig()
got := cfg.Server.Port
if (err != nil) != tt.wantErr {
t.Errorf("want error : %v ,but : %v", tt.wantErr, err)
}
if got != tt.want {
t.Errorf("want %v , but got : %v", tt.want, got)
}
})
}
}
t.Setenvメソッドによって、テスト実行中のみ環境変数を設定することができます。
テストでは、PORTというキーに":8081"という環境変数を設定してそれをConfig構造体に埋め込まれたserver構造体の中のPortフィールドから取得できるかを検証しています。
main関数でConfigを利用する
アプリケーションの起動時にConfig構造体を初期化し、データベースやキーバリューストアの接続処理を行う関数などに環境変数を渡すようにします。
mainパッケージに定義するmain()関数は、実行後、一番初めに呼び出されるものであり、プログラムのエントリーポイントとなります。
// app/main.go
package main
import (
"context"
"log"
"github.com/kakkky/app/config"
)
func main() {
ctx := context.Background()
// 環境変数を読み込む
cfg, err := config.NewConfig()
if err != nil {
log.Fatalf("failed to read config : %v", err)
}
// 環境変数を渡す
run(ctx, cfg)
}
// run関数の中でさまざまな接続処理を行う(中身は未実装)
func run(ctx context.Context, cfg *config.Config) {
// DB接続
// Redis接続
// HTTPサーバー起動
}
main()関数の中でconfig.NewConfig()関数を呼び出し、環境変数を読み込んでいます。それをcfg変数としてrun()関数に渡しています。
run()関数の中では、DB接続、Redis接続、HTTPサーバー起動などインフラストラクチャのレイヤーの処理をいくつか呼び出します。このcfgはrun()関数内で呼び出される接続処理の際に使用していきます。
現段階のディレクトリ
.
├── .github
│ └── workflows
│ └── test.yml
├── .gitignore
├── Dockerfile.app
├── Makefile
├── app
│ ├── .air.toml
│ ├── application
│ │ └── usecase
│ │ ├── task
│ │ │ ├── create_task_usecase.go
│ │ │ ├── create_task_usecase_dto.go
│ │ │ ├── create_task_usecase_test.go
│ │ │ ├── delete_task_usecase.go
│ │ │ ├── delete_task_usecase_dto.go
│ │ │ ├── delete_task_usecase_test.go
│ │ │ ├── fetch_task_usecase.go
│ │ │ ├── fetch_task_usecase_dto.go
│ │ │ ├── fetch_task_usecase_test.go
│ │ │ ├── fetch_tasks_usecase.go
│ │ │ ├── fetch_tasks_usecase_test.go
│ │ │ ├── fetch_user_tasks_usecase.go
│ │ │ ├── fetch_user_tasks_usecase_dto.go
│ │ │ ├── fetch_user_tasks_usecase_test.go
│ │ │ ├── interface_task_query_service.go
│ │ │ ├── mock_task_query_service.go
│ │ │ ├── update_task_state_usecase.go
│ │ │ ├── update_task_state_usecase_dto.go
│ │ │ └── update_task_state_usecase_test.go
│ │ └── user
│ │ ├── fetch_users_usecase.go
│ │ ├── fetch_users_usecase_dto.go
│ │ ├── fetch_users_usecase_test.go
│ │ ├── register_usecase.go
│ │ ├── register_usecase_dto.go
│ │ ├── register_usecase_test.go
│ │ ├── unregister_usecase.go
│ │ ├── unregister_usecase_dto.go
│ │ ├── unregister_usecase_test.go
│ │ ├── update_profile_usecase.go
│ │ ├── update_profile_usecase_dto.go
│ │ └── update_profile_usecase_test.go
│ ├── config
│ │ ├── config.go
│ │ └── config_test.go
│ ├── domain
│ │ ├── errors
│ │ │ ├── errors.go
│ │ │ └── errors_test.go
│ │ ├── task
│ │ │ ├── interface_task_repository.go
│ │ │ ├── mock_task_repository.go
│ │ │ ├── task.go
│ │ │ ├── task_content.go
│ │ │ ├── task_state.go
│ │ │ └── task_test.go
│ │ └── user
│ │ ├── interface_user_domain_service.go
│ │ ├── interface_user_repository.go
│ │ ├── mock_user_domain_service.go
│ │ ├── mock_user_repository.go
│ │ ├── user.go
│ │ ├── user_domain_service.go
│ │ ├── user_domain_service_test.go
│ │ ├── user_email.go
│ │ ├── user_hashed_password.go
│ │ ├── user_test.go
│ │ └── users.go
│ ├── go.mod
│ ├── go.sum
│ ├── infrastructure
│ │ └── db
│ │ └── mysql
│ │ └── conf.d
│ │ └── my.conf
│ ├── main.go
│ └── tmp
│ ├── build-errors.log
│ └── main
├── compose.yml
├── go.work
├── go.work.sum
└── pkg
├── go.mod
├── go.sum
├── hash
│ ├── hash.go
│ └── hash_test.go
└── ulid
├── ulid.go
└── ulid_test.go
次章では、データベース・キーバリューストアへの接続やマイグレーションによるテーブル作成などを行なっていきます。