プロジェクト構成
Goでは1つのプロジェクトのレイアウトがある。
ただし、このレイアウトを使わないといけないという規制はない。
また、以下のGo moduleのドキュメントも参考になる。
project-layoutの主なディレクトリ
以下は、project-layout のレイアウトの主なディレクトリ。
project-layout は、Go公式のレイアウトではないので、必ずしも守らないといけない規約ではない。
-
/cmd
: アプリケーションごとにコマンドのmain.goを置く -
/internal
: 外部に公開したくないプライベートなコードを置く -
/pkg
: 公開したいパッケージを置く -
/test
: 追加の外部テストとデータ(結合テストや公開APIテスト)単体テストは入れない -
/configs
: 設定ファイル -
/docs
: 設計ドキュメント、ユーザドキュメント -
/examples
: アプリケーション、公開ライブラリのコード例 -
/api
: SwaggerやProtocol Buffersなどを置く -
/web
: ウェブアプリケーション固有のアセット(静的ファイルなど) -
/build
: パッケージングと継続的インテグレーションのファイル -
/scripts
: 解析やインストール用のスクリプト -
/vendor
: アプリケーションが依存しているもの(Goの依存モジュールなど) -
/deployments
: IaaS、PaaS、システム、コンテナオーケストレーションのデプロイメント設定とテンプレート(/deploy
とすることもある) -
/tools
: プロジェクトをサポートするツール
src
は作ってはいけない。srcを使うのはJavaのスタイルなので、Goで使用してはいけない。また、src
は一般的すぎる。
シンプルなパッケージ
ここではfoo
というパッケージを作成するとする。
project
├── foo.go
├── foo_test.go
└── go.mod
foo.go
に以下の行を付け加える。
package foo
クライアントはimport "github.com/user_name/foo"
とすると、使用することができる。
以下のように複数のファイルに分けることはできるが、基本的にはすべて同じパッケージである。
project
├── foo.go
├── foo_test.go
├── hoge.go
├── hoge_test.go
├── fuga.go
├── fuga_test.go
└── go.mod
シンプルなコマンド
foo
というコマンドを作成するとする。
project
├── auth.go
├── auth_test.go
├── client.go
├── main.go
└── go.mod
go modに、module github.com/user_name/foo
を記載する。
以下のコマンドでクライアントはインストールできる。
go install github.com/user_name/foo@latest
ちなみに、1つのファイルを複数のgoに分けたときは、go run main.go
を使ってはいけない。
APIサーバ
APIサーバでは外部に公開するパッケージはないので、すべてinternal/
における。
project/
├── cmd/
│ ├── api-server/
│ │ └── main.go
│ └── metrics-analyzer/
│ └── main.go
├── internal/
│ ├── auth/
│ └──・・・
│ └──metrics/
│ └──・・・
└── go.mod
パッケージ
パッケージのわけ方はレイヤーごとに分けたり、ドメインごとに分けたりする方法があるが、決まりはない。1つのパッケージの中のファイルが多すぎたり、少なすぎたりしてはいけない。適切な粒度にする。
ユーティリティパッケージ
utils, common, base, shared といった共有パッケージは作成してはいけない。
package util
func NewStringSet(...string) map[string]struct{} { return nil }
func SortStringSet(map[string]struct{}) []string { return nil }
set := util.NewStringSet("c", "a", "b")
fmt.Println(util.SortStringSet(set)
これをリファクタして、stringset
というパッケージを作成する
package stringset
func New(...string) Set { return nil }
func Sort(map[string]struct{}) []string { return nil }
set := stringset.New("c", "a", "b")
fmt.Println(stringset.Sort(set))
さらに struct を作成して、関数をメソッドにする。
package stringset
type Set map[string]struct{}
func New(...string) Set { return nil }
func (s Set) Sort() []string { return nil }
set := stringset.New("c", "a", "b")
fmt.Println(set.Sort())
意味のないパッケージ名はつけてはいけない。
また、パッケージの名前は何を含むかではなく、何を提供するかで決める。
パッケージ名の衝突
例えば、foo
というパッケージをインポートして、foo
という変数を作成すると、名前がコンフリクトしてしまう。
import foo
func main() {
foo := foo.New()
}
foo
という変数を宣言した後はfooパッケージにアクセスすることはできない。
異なる名前をつける以外の方法として、エイリアスを使う方法がある。
import pkgfoo "foo"
func main() {
foo := pkgfoo.New()
}
また、パッケージ以外にも、組み込み関数と変数の名前の衝突も避ける。(copy()
など)