この章で行うこと

  • 環境変数を管理する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

これらをアプリケーション内で効率的に管理するためには、サードパーティのパッケージを使うと便利です。今回は以下のパッケージを使用します。

https://github.com/kelseyhightower/envconfig

以下のコマンドを打って、インストールしておいてください。

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()関数は、実行後、一番初めに呼び出されるものであり、プログラムのエントリーポイントとなります。

https://recursionist.io/learn/languages/go/others/package
// 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

次章では、データベース・キーバリューストアへの接続やマイグレーションによるテーブル作成などを行なっていきます。