Go言語のプロジェクト構成 - 公式のススメ
有名なGoのプロジェクト構成のパターンとしては、以下のgolang-standardsが有名です。
このパターンは賛否両論ありつつも長い間多くのプロジェクトで採用されてきたのですが、Go公式のものではありません。
そして Goの公式はこれまで、プロジェクト構成に関するドキュメントを公開してきませんでした。
しかしここにきて、公式ドキュメントが公開されました。 それがこれです。
本記事ではこの公式ドキュメントが勧めるプロジェクト構成を、和訳しつつかいつまんで解説します。
単一のパッケージ
全てのコードをプロジェクトのルートディレクトリに収めます。
project-root-directory/ go.mod modname.go modname_test.go
このパッケージがGitHubにgithub.com/someuser/modname
という形でアップロードされた場合、go.modファイルに表記されるモジュール名はgithub.com/someuser/modname
になります。
modname.goはパッケージ名を以下のように宣言します。
package modname // ... package code here
このパッケージをimportする時は以下のような感じになります。
import "github.com/someuser/modname"
以下のようにコードを複数のファイルに分割することもできます。この場合も、パッケージ宣言は全ファイルでpackage modname
となります。
project-root-directory/ go.mod modname.go modname_test.go auth.go auth_test.go hash.go hash_test.go
単一のコマンド
実行可能プログラム(コマンドラインツール)は、main
関数が定義されたファイルを中心に構成されます。
コード規模が大きい場合はファイルを分割しても良いですが、全てのファイルでpackage main
のパッケージ宣言が必要です。
project-root-directory/ go.mod auth.go auth_test.go client.go main.go
上記の例ではmain.goがmain関数を持っている想定ですが、これはGo言語の慣習によるものです。main関数を持つファイル名は別名(modname.goなど)でもかまいません。
GitHubの github.com/someuser/modname
リポジトリにアップロードされた場合、go.modファイルのモジュール名は以下のようになります。
module github.com/someuser/modname
このモジュールのユーザは以下のようにしてコマンドをインストールします。
$ go install github.com/someuser/modname@latest
内部にサブパッケージを持つパッケージ/コマンド
規模の大きなパッケージやコマンドは、内部的に使うコードを複数のパッケージに分割することがあります。
こういった内部利用のためのパッケージは、internal
ディレクトリの配下に置くことが推奨されています。
internal配下のパッケージは、モジュールの外部からimportすることができなくなります。
internal配下はモジュール外部に影響しないことが確約されているため、リファクタリングなどの作業がスムーズになります。
(下記に記載されている通り、internal配下のパッケージは、internalの親ディレクトリ配下のコードからのみimportできます。)
この場合のプロジェクト構成は下記のようになります。
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からauthパッケージをimportすることはできますが、project-root-directory外部のパッケージからimportすることはできません。
import "github.com/someuser/modname/internal/auth" // 内部からのみ可能
複数のパッケージ
モジュールが複数のimport可能なパッケージで構成されていることもあります。以下のような構成の時です。
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
modnameパッケージは通常通り、以下のようにimportできます。
import "github.com/someuser/modname"
同梱されているサブパッケージは、以下のようにimportできます。
import "github.com/someuser/modname/auth" import "github.com/someuser/modname/auth/token" import "github.com/someuser/modname/hash"
internalディレクトリ以下はimportできません。internal配下に隠蔽できるコードは、できるだけ入れてしまうことが推奨されています。
複数のコマンド
次は、モジュールが複数のimport可能なコマンドで構成されているパターンです。
この場合も、コマンドごとに別のディレクトリで構成されます。
project-root-directory/ go.mod internal/ ... shared internal packages 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
ディレクトリに入れてしまいます。
コマンドだけで構成されているリポジトリの場合、この慣習には必ずしも従う必要はないです。
しかし、コマンドとimport可能パッケージが混在するリポジトリの場合では、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できます。
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
サーバプロジェクト
サーバプロジェクトは通常export用のパッケージは持ちません。そのため、サーバのロジックを実装したパッケージはinternalディレクトリ配下に置くのが望ましいです。
またプロジェクトはGo言語ではないファイルで構成されたディレクトリも多く持ちうるため、Goのコマンドは全てcmdディレクトリ内に置くのが良いでしょう。
project-root-directory/ go.mod internal/ auth/ ... metrics/ ... model/ ... cmd/ api-server/ main.go metrics-analyzer/ main.go ... ... the project's other directories with non-Go code
サーバのリポジトリ内で他プロジェクトにも流用できそうなパッケージを作成した場合は、モジュールごと分けてしまうのがベストです。
以上、Goらしく非常にシンプルなドキュメントになっています。
今までの実質的なスタンダードだったgolang-standardsでもcmd
やinternal
の使用は推奨していますし、そこまで劇的に異なっているというわけでもなさそうです。
ただ公式がドキュメントを公開したということは、より不動の立ち位置をもつスタンダードが確立したという意味で大きな意義がありそうですね。
Go初学者にもまずこの公式ドキュメントを理解してもらった上で、場合によってはgolang-standardsの方も理解してもらうといった流れもできそうで
公式の見解が示されるというのは学習の指針も明確になりますし皆にとって良いことなのではないかと思います。