【翻訳】Goモジュールの構成(Organizing a Go module)
Go言語公式から、Organizing a Go module という、Goプロジェクトのファイル・ディレクトリ構成に関する記事が発表されました。
公式からGoプロジェクトの構成に関する指針が紹介されるのは(自分の知る限り)初めてのことだったので、精読がてら翻訳しました。
注意
訳文中では、「コマンドラインプログラム」あるいは単に「プログラム」と言った場合、コマンドを叩いて実行するプログラムを指します。
翻訳
「Go プロジェクトはどういうファイル・ディレクトリ構成にするべきか」は、多くのGo 入門者が抱く疑問です。このドキュメントの目的は、この疑問に対する指針を提供することです。Go Module の基本的な知識を持っていると、よりこのドキュメントを理解しやすいかと思います。Tutorial: Create a Go module や Managing module source に説明があります。
このガイドラインでは、パッケージ、コマンドラインプログラム、またそれら2つが混在したプロジェクトなど、Go プロジェクトの種類ごとに指針を説明します。
パッケージの基本構成
基本的な Go パッケージでは、プロジェクトのルートディレクトリにすべてのコードを置き、モジュール・パッケージは1つだけで構成します。例えば、1つの.go
ファイルしかないパッケージでは、プロジェクト構成は以下のようになります。
project-root-directory/
go.mod
modname.go
modname_test.go
(ドキュメントを通して、ファイル・パッケージ名は任意です)
このディレクトリをgithub.com/someuser/modname
というGitHubリポジトリにアップロードする場合、go.mod
ファイルには
module github.com/someuser/modname
と書き、modname.go
で以下のようにパッケージを宣言します。
package modname
// 以下パッケージのコード本体
このパッケージのユーザーは、以下のようにすることでパッケージを使用できます。
import "github.com/someuser/modname"
Go パッケージのコードは、同ディレクトリの別ファイルに分割できます。例えば、上のmodname
パッケージのコードは、以下のように分割してもよいです。
project-root-directory/
go.mod
modname.go
modname_test.go
auth.go
auth_test.go
hash.go
hash_test.go
この場合、各.go
ファイルでpackage modname
と宣言することになります。
コマンドラインプログラムの基本構成
コマンドラインプログラムのディレクトリ構成は、コードの規模と複雑さによって変わります。簡単なプログラムなら、main
関数を定義する.go
ファイル1つだけで十分です。大規模なプログラムでは、コードを複数ファイルに分割して、各ファイルでpackage main
と宣言します。
project-root-directory/
go.mod
auth.go
auth_test.go
client.go
main.go
上のファイル構成では、main
関数を含むのはmain.go
ですが、これは慣習的な命名です。main.go
の代わりにmodname.go
など任意の名前を使用できます。
このディレクトリをgithub.com/someuser/modname
という GitHub リポジトリにアップロードする場合、go.mod
には以下のように記述します。
module github.com/someuser/modname
ユーザーは以下のコマンドでこのプログラムをローカルにインストールできます。
$ go install github.com/someuser/modname@latest
ユーティリティパッケージを含むパッケージ・コマンドラインプログラム
大規模なパッケージ・コマンドラインプログラムでは、一部の機能をユーティリティパッケージに分割することがあります。そういったパッケージはinternal
ディレクトリに配置することを推奨します。そうすることで、外部からの利用を想定していないパッケージに他の Go プロジェクトが依存してしまうことを防げます (参考)。他モジュールはinternal
ディレクトリのコードをimport
できないので、APIなどを変更しても、外部のコードに影響が及ぶことはありません。この場合、ディレクトリ構成は以下のようになります。
project-root-directory/
internal/
auth/
auth.go
auth_test.go
hash/
hash.go
hash_test.go
go.mod
modname.go
modname_test.go
modname.go
では、package modname
、auth.go
では、package auth
というように宣言します。modname.go
は、以下のようにしてauth
パッケージをインポートできます。
import "github.com/someuser/modname/internal/auth"
コマンドラインプログラムの場合も同様に、internal
ディレクトリにユーティリティパッケージを置きます。ただし、この場合はルートディレクトリのファイルはpackage main
と宣言します。
複数のパッケージを含むモジュール
1つのGo モジュールに、複数の公開パッケージがある場合です。この場合、1パッケージに1つのディレクトリを対応させます。ディレクトリ構成は階層状になっていてもよいです。例えば、以下のような構成が考えられます。
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
token/
token.go
token_test.go
hash/
hash.go
internal/
trace/
trace.go
go.mod
に
module github.com/someuser/modname
と記述されているとすると、ルートディレクトリの.go
ファイルでpackage modname
と宣言することで、ユーザーはこのパッケージを
import "github.com/someuser/modname"
でインポートできます。また、他のパッケージは
import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"
でインポートできます。
internal/trace
内のtrace
パッケージは、外部のパッケージからはimport
できません。外部に公開する必要のないパッケージは、可能な限りinternal
に格納するべきです。
複数のコマンドラインプログラムを含むモジュール
同じリポジトリに複数のコマンドラインプログラムがある場合、各プログラムは別々のディレクトリに置かれることが多いです。
project-root-directory/
go.mod
internal/
... ユーティリティパッケージ
prog1/
main.go
prog2/
main.go
各ディレクトリの、プログラムを記述した.go
ファイルではpackage main
と宣言します。internal
ディレクトリには、コマンドラインプログラム間で共有されるユーティリティパッケージを置きます。
ユーザーは、
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
とすることで、それぞれのプログラムを使用できます。
複数のコマンドラインプログラムがある場合、各プログラムのディレクトリをすべてcmd
ディレクトリに格納してしまう慣習もあります。コマンドラインプログラムしか含まないリポジトリでは必ずしもこうする必要はありませんが、パッケージとコマンドラインプログラムが混在する次のようなケースでは便利です。
パッケージとコマンドラインプログラムが混在するモジュール
同じリポジトリに、パッケージとコマンドラインプログラムが混在することもあります。例えば、以下のような構成が考えられます。
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
internal/
... internal packages
cmd/
prog1/
main.go
prog2/
main.go
このモジュールがgithub.com/someuser/modname
という名前だったとすると、ユーザーは以下のようにパッケージをインポートできます。
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"
また、以下でプログラムをインストールできます。
$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest
サーバー
多くのプロジェクトが Go でサーバーを実装しています。サーバーには、プロトコル(REST・gRPCなど)・デプロイ方法・フロントエンド・コンテナ・その他のスクリプトなどによって、さまざまな構成がありえます。ここでは、サーバープロジェクトのうち Go で実装された部分に絞って説明します。
サーバーはそれ自体で完結したプログラムで、パッケージを export しない場合が多いです。その場合、Go サーバーのパッケージはinternal
ディレクトリに置くことを推奨します。また、多くの場合は Go 以外のコードを含むディレクトリが別にあるので、Go サーバーのプログラムはcmd
ディレクトリに格納するとよいです。
project-root-directory/
go.mod
internal/
auth/
...
metrics/
...
model/
...
cmd/
api-server/
main.go
metrics-analyzer/
main.go
...
... その他、Go 以外のコードを格納するディレクトリ
プロジェクトが大規模になって、パッケージが他プロジェクトでも使いまわせるほどに成長したら、それらを別のモジュールに切り出すのがいいでしょう。
Discussion