Go言語のプロジェクト構成 – 公式のススメ

golang logo プログラミング

有名なGoのプロジェクト構成のパターンとしては、以下のgolang-standardsが有名です。

GitHub - golang-standards/project-layout: Standard Go Project Layout
Standard Go Project Layout. Contribute to golang-standards/project-layout development by creating an account on GitHub.

このパターンは賛否両論ありつつも長い間多くのプロジェクトで採用されてきたのですが、Go公式のものではありません。

そして Goの公式はこれまで、プロジェクト構成に関するドキュメントを公開してきませんでした。

しかしここにきて、公式ドキュメントが公開されました。 それがこれです。

Organizing a Go module - The Go Programming Language

本記事ではこの公式ドキュメントが勧めるプロジェクト構成を、和訳しつつかいつまんで解説します。


単一のパッケージ

全てのコードをプロジェクトのルートディレクトリに収めます。

project-root-directory/
  go.mod
  modname.go
  modname_test.go

このパッケージがGitHubにgithub.com/someuser/modnameという形でアップロードされた場合、go.modファイルに表記されるモジュール名はgithub.com/someuser/modnameになります。

modname.goはパッケージ名を以下のように宣言します。

Go
package modname

// ... package code here

このパッケージをimportする時は以下のような感じになります。

Go
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ファイルのモジュール名は以下のようになります。

Go
module github.com/someuser/modname

このモジュールのユーザは以下のようにしてコマンドをインストールします。

Bash
$ go install github.com/someuser/modname@latest

内部にサブパッケージを持つパッケージ/コマンド

規模の大きなパッケージやコマンドは、内部的に使うコードを複数のパッケージに分割することがあります。

こういった内部利用のためのパッケージは、internalディレクトリの配下に置くことが推奨されています。

internal配下のパッケージは、モジュールの外部からimportすることができなくなります。

internal配下はモジュール外部に影響しないことが確約されているため、リファクタリングなどの作業がスムーズになります。

(下記に記載されている通り、internal配下のパッケージは、internalの親ディレクトリ配下のコードからのみimportできます。

go command - cmd/go - Go Packages
Go is a tool for managing Go source code.

この場合のプロジェクト構成は下記のようになります。

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することはできません。

Go
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できます。

Go
import "github.com/someuser/modname"

同梱されているサブパッケージは、以下のようにimportできます。

Go
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は例によって、コマンド横断で共通して使うパッケージを格納します。

コマンドのインストールは以下のようになります。

Bash
$ 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できます。

Go
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"

また以下のようにコマンドのインストールもできます。

Bash
$ 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でもcmdinternalの使用は推奨していますし、そこまで劇的に異なっているというわけでもなさそうです。

ただ公式がドキュメントを公開したということは、より不動の立ち位置をもつスタンダードが確立したという意味で大きな意義がありそうですね。

Go初学者にもまずこの公式ドキュメントを理解してもらった上で、場合によってはgolang-standardsの方も理解してもらうといった流れもできそうで

公式の見解が示されるというのは学習の指針も明確になりますし皆にとって良いことなのではないかと思います。

コメント

タイトルとURLをコピーしました