🚴

Goモノレポ開発の落とし穴 go.workファイルはコミットすべきか?

に公開

go.workとは

go.work は、Go 1.18で導入された設定ファイルです。
複数のGoモジュールをまとめて1つのワークスペースとして管理できるようになります。

これにより、モノレポ構成で複数のモジュールを跨ぐ開発が効率化されます。
https://go.dev/blog/get-familiar-with-workspaces

使い方を学ぶ

初期化

以下を実行すると、カレントディレクトリに go.work ファイルが生成されます。

go work init

モジュールを追加

ワークスペースにモジュールを追加するには use を使います。

go work use ./moduleA

go.workファイルの例

go 1.24.0

use (
	./moduleA
	./moduleB
)

依存関係の同期

go mod sync

これにより、ワークスペースにあるモジュールの依存パッケージをまとめて取得できます。

各モジュールが個別に取得するのではなく、共有キャッシュを使うため効率的です。

一見すると go.work は非常に便利で、リモートにコミットして共有すべきようにも思えます。

しかし、実際にはそうではありません。

go.workはコミットするべき?

It is generally inadvisable to commit go.work files into version control systems, for two reasons:

A checked-in go.work file might override a developer’s own go.work file from a parent directory, causing confusion when their use directives don’t apply.
A checked-in go.work file may cause a continuous integration (CI) system to select and thus test the wrong versions of a module’s dependencies. CI systems should generally not be allowed to use the go.work file so that they can test the behavior of the module as it would be used when required by other modules, where a go.work file within the module has no effect.
That said, there are some cases where committing a go.work file makes sense. For example, when the modules in a repository are developed exclusively with each other but not together with external modules, there may not be a reason the developer would want to use a different combination of modules in a workspace. In that case, the module author should ensure the individual modules are tested and released properly.

引用:https://go.dev/ref/mod#workspaces

日本語訳

go.work ファイルをバージョン管理システムにコミットすることは、一般的には推奨されません。その理由は 2 つあります。

チェックインされた go.work ファイルは、親ディレクトリにある開発者自身の go.work ファイルを上書きする可能性があります。その結果、use ディレクティブが適用されない場合に混乱が生じる可能性があります。
チェックインされた go.work ファイルは、継続的インテグレーション (CI) システムがモジュールの依存関係の誤ったバージョンを選択し、テストしてしまう可能性があります。CI システムでは、通常、go.work ファイルの使用を許可すべきではありません。これは、モジュール内の go.work ファイルが他のモジュールから要求された場合に、そのモジュールの動作をテストできるようにするためです。モジュール内の go.work ファイルは影響を与えません。
ただし、go.work ファイルをコミットすることが理にかなっている場合もあります。たとえば、リポジトリ内のモジュールが互いに排他的に開発され、外部モジュールとは一緒に開発されていない場合、開発者がワークスペース内で異なるモジュールの組み合わせを使用する理由がない可能性があります。そのような場合、モジュール作成者は個々のモジュールが適切にテストされ、リリースされていることを確認する必要があります。

公式リファレンスでも下記の理由からgo.workをコミットすることは推奨されていません。

  • 開発者ごとのローカル環境に合わせたgo.workの設定が意図せず上書きされ、不要なエラーや混乱を引き起こす可能性
  • CI環境で依存関係の検証が適切に行われないリスク

また、必ずしもgo.workをコミットすることが悪いわけではなく、理にかなっているケースもあるため、どう判断するかはプロジェクトによって異なるようです。
例えば、モノレポ構成であっても、同一のGoパッケージを扱う複数のモジュールを開発している場合には、go.workによって共通依存のビルドやテストを効率化できるため、コミットするのも合理的です。
一方で、異なるパッケージ間の依存が複雑に絡み合うような構成では、go.work によるローカルパス解決が思わぬ副作用を生むことがあり、むしろビルド環境を分離した方が望ましいケースも少なくありません。
つまり、「モノレポだからgo.workをコミットする」という単純な判断ではなく、パッケージの再利用構造や開発体制に応じて、採用の是非を慎重に判断することが重要です。

私が go.work のコミットを避けたい理由

私は過去の経験から、go.workを基本的にはコミットすべきではないと感じました。

それは、ワークスペースで管理されたモジュールの一部でPrivate Repositoryで管理しているパッケージを使用しようとしたときに、ワークスペースに所属するモジュールに関連するCIが全て失敗したときです。

Private Repositoryのパッケージを取得するには、CIでGOPRIVATE環境変数を設定し、認証情報をセットするステップを必要とします。
本来、修正が必要なのは、特定のモジュールに関連するCIの1つのみでしたが、go.workがあることで関係のないCIでもエラーが出ることになりました。

解決策として、全てのCIのステップにPrivate Repositoryの取得のためのステップを追加することもできますが、この修正は適切ではありません。

モノレポで管理されているプロジェクトで、このように異なるモジュールを使用する場合には、go.workを使用することはCI上、効率的ではなく、不要なパッケージを取得する必要があり、非効率的になってしまいます。

go.workをコミットすることに必ずしも問題があるとは言い切れません。

そのため、既にコミットされているプロジェクトでは、何か問題が発生してからの対処でも問題がないと考えます。

一方で、初期構築時には将来的に問題が発生するリスクをなくすためにコミットしないことを推奨します。

go.work を削除する際の注意点

go.workをコミット済みのプロジェクトから削除するのは、大きな影響を及ぼします。

弊社のあるプロジェクトでは、go.work を削除した直後に 13個のCIが失敗しました。

ただし、修正後は CI が対象モジュールだけに限定され、ビルド時間も短縮されるという副次的なメリットもありました。

再発防止のため .gitignore に以下を追記しましょう:

# This project does not use go.work. Please do not add go.work files.
# <https://go.dev/ref/mod#workspaces>
go.work
go.work.sum

ローカル開発では便利に使おう

go.work のコミットは避けるべきですが、ローカルでは問題なく使えます

Makefileに以下のようなコマンドを用意しておくと、開発者体験を損なわず便利です。

init-go-workspace:
	go work init \
	 ./moduleA \
	 ./moduleB

Discussion