Go langのモジュールとは、go.modとgo.sumも併せて初心者でもわかるように解説してみる

記事タイトルとURLをコピーする

モジュールをなんとなく使っている

こんにちは。普段はRubyを触っているのですが、最近はGoにディープダイブしたいサービス開発部の布施です。

Goでコーディングする時に欠かせないのがモジュールという概念です。 慣れている方なら特段困ることはありませんが、 これからGoのことを学んだり、Goのはじめの一歩を踏み出された方はなかなか曖昧な知識でモジュールを触らなくてはいけないこともあるかと思います。

今回はそんな方に向けて、モジュールの役割をブログにしたためようと思います。

ライブラリ、モジュール、パッケージ、の関係性

そもそもプログラムの世界には頻繁に「ライブラリ」「パッケージ」「モジュール」という概念が出てきます。言語によって使われ方は様々なようですが、これらはただのプログラムたちをひとまとめにした単位、でしかありません。例えば1つのファイルを1つのモジュールとしたり、複数のモジュールを1つのパッケージとしたり。よくあるのはライブラリ > パッケージ > モジュールと内包されているパターンです。

ところでGoではこの順番が違います。モジュール > パッケージの順です。

さらにいうとGoではライブラリという概念は明確には登場しません。つまるところ、モジュール > パッケージの2階層が理解できれば大丈夫です。

Goにおけるモジュールとは

概念を説明するだけだとわかりづらいので、ここからは「架空の天気予報WebAPIを叩くツール」を例に出して解説をしていきます。

まずはこのツールに名前をつけてあげます。今回は「get-weather」とでもしてリポジトリを作成します。

この命名はgo.modというファイルの1行目に記載をします。

実はこの1行でリポジトリが「get-weather」モジュールとして扱われるようになります。 つまり、モジュールは1モジュール ≒ 1リポジトリと対応するような概念です。

※「ような」と書いたのは、厳密には1リポジトリの中で複数のモジュールを定義することも十分に可能だからです。Goでは1.18からWorkspace modeという機能が導入されており、1リポジトリ内で複数のモジュールを容易に管理することが可能になりました。ですが、ここでは一旦モジュールとリポジトリが1:1対応だと理解しておくと全体像が掴みやすいと思うので、上記のような記載をしました。

ところでget-weatherというモジュール名にはgithub.com/hogehoge/というパスをつけています。これは「github上のhogehogeというユーザー or 組織に所属するget-weatherというリポジトリだよ」という意味です。言ったら住所みたいなものですね。

この住所を含めたモジュール名は世界中で一意でないといけません。例えば、github.com/hogehoge/get-weatherという名前のモジュールがこの世に2つ存在してしまったとしましょう。詳しくは後述しますが、モジュールは外部のアプリケーションから呼び出すことが可能です。誰かがこのget-weatherモジュールを使って新しいツールを開発したい時、「github.com/hogehoge/get-weatherを使いたいぞ!」という宣言をする必要があります。しかし、同じ名前のモジュールが2つ存在すると、どっちのmoduleを使えば良いかわからず、ややこしくなりますよね。なのでパスを含めたモジュール名は一意である必要があります。

外部モジュールの読み込みとgo.mod

次に、このget-weatherツールでは誰かが作成したモジュール(外部モジュール)を使用します。モジュールは外部モジュールを自分のアプリケーションの中で使うこともできますし、自分が作成したモジュールを誰かに使ってもらうことも可能です。

get-weatherツールでは

  • REST API用のモジュール: github.com/go-resty/resty/v2
  • config管理のモジュール: github.com/spf13/viper

あたりを使わせていただくことにしましょう。これらを使用するためには.goファイルでimportという記載をします。

こんな感じ。

今回はmain.goというファイルでimportをしています。これはmain.goファイル内でrestyやviperで定義されている識別子(定数、変数、型、関数、メソッドなど)を使用しますよという宣言です。本来import文にはモジュールではなくパッケージ名を記載するのですが、restyとviperはモジュール名とパッケージ名が同一なのでimport文でさもモジュールを記載しているように書けます。

さてさて、残念なことにこれだけではモジュールを使うことはできません。次はget-weather全体で「このモジュールを使うよ!」という宣言を書く必要があります。

ここで登場するのがgo mod tidyというコマンドです。このコマンドは、リポジトリ内の.goファイルを見てimportされているモジュールをgo.modに記載してくれます。

この時に依存関係も記載してくれます。つまり、restyやviperの中で使っているモジュールも併せてダウンロードしてくれます。今回は間接的に使用しているモジュールが複数あったので図では見切っています。

さらに、go.modに記載されたモジュールが開発環境にダウンロードされていなければそれらをダウンロードします。ダウンロードされるディレクトリは、Go1.11以降ではデフォルトで$GOPATH/pkg/modです。長くなるので割愛しますがこの辺りは「モジュール対応モード」によって異なるので、興味のある方は調べてみてください。

さて話は飛びますが、Goはコンパイラ言語なので実行ファイル(バイナリファイル)を作成するために「ビルド」という処理が必要になります。イメージとしては、自分で作成したプログラムと外部モジュールががっちゃんこして実行ファイルを作成する感じです。

この時、「がっちゃんこするモジュール」はgo.modにかかれているものです。つまり、go.modに書かれていないモジュールがimportに含まれている状態でビルドをしようとするとエラーが出ます

ただし、ビルドに必要なモジュールがローカル環境に存在しない状態であるが、go.modに必要なモジュールの記載はされている状態でビルドをすると、go.modを参考にダウンロードを自動的にしてくれます。

この状況は開発に途中から参入するパターンで役に立ちます。

例えば、get-weatherツールの開発にBさんが途中から参入しました。Bさんはgit cloneでリポジトリをダウンロードします。このあと、明示的にgo mod tidyをしなくてもgo.modにモジュールの依存関係が書かれていればgo build時に勝手にモジュールをダウンロードしてくれるのでビルドエラーは発生しません。

go.sumとは

次にgo.sumについて解説します。 go.sumはチェックサムファイルであり、外部モジュールが不正に改竄されていないことを保証するためのファイルです。

go.sumには使用する外部モジュールとチェックサムが記載されています。 このチェックサムは「外部モジュールの本体となるプログラムとバージョンから生成される文字列」のことです。 そのため任意のバージョンに対して一意のチェックサムが生成されます。

整合性が取れているとはどういうことでしょうか。 具体的な役割は架空のモジュールA(1.5.0)を例に出して紹介します。 まずは、Bさんは新たに.goファイルでモジュールAをimportに記載し、go mod tidyを実行します。 (チェックサムの生成方法はいくつかありますが、go mod tidyはその1つです) この時点ではモジュールAは改竄前なので、チュックサム(正常)が生成されます。 このリポジトリをBさんはgit pushします。

このあとモジュールAがなんらかの形で改竄されてしまったとしましょう。 バージョンは特に変更されずにプログラムだけ改竄されるイメージです。

その後、新たに開発に参加したCさんがget-weatherのリポジトリをgit cloneします。 このリポジトリのgo.sumには改竄前に作成されたチェックサム(正常)が記載されています。

このあと、Cさんの環境でgo mod tidyをしようとすると、ダウンロードされるモジュールのチェックサム(異常)が生成されます。 そして、チェックサム(異常)がgo.sumで記載されているチェックサム(正常)と整合性が取れているのかを確認します。 ここで、2つは異なる値であるので改竄があったことを検知することができる仕組みです。

つまるところ、go.sumは改竄されたモジュールを使用することを防ぐことができるためのファイルです。

終わりに

まとめです。モジュールとその周辺で使われるコマンドやファイルについてつらつらと紹介してきました。

  • moduleは厳密には違うが1リポジトリに対応するような概念であることが多い
  • go.modで自分のモジュール名を表現し、さらに外部モジュールの管理をする
  • go mod tidyでimportするべき外部モジュールをgo modに記載し、依存関係を解消する
  • go.sumは外部モジュールが改竄されていないことを確認するためのチェックサムファイル

詳細な情報はReferenceも併せてご覧ください。
(特にgo mod tidyは本記事で載せきれなかった"不要なモジュールの削除"などの役割も持っています)

go.dev

この記事が誰かの助けになれば幸いです。

ふせ ゆきひろ(執筆記事の一覧)

サービス開発部 2022年新卒入社

好きな散歩エリアは谷中銀座です。