ふわっとテック日記

テック系のことをメインに書いていきます。go言語/typescript/javascriptが好きです。たまに趣味(筋トレ)の話や日常系の記事も書きたいな〜と思っています。

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

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

github.com

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

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


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

go.dev


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


単一のパッケージ

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

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

このパッケージがGitHubgithub.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できます。

pkg.go.dev

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

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

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


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

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