Goモジュールを作成してみた

# 背景・目的
[前回](https://qiita.com/zumax/items/040c3cce24eeb6727679)、Goについて簡単なプログラムを書いてみました。今回はGoモジュールを作成てしみます


# まとめ
下記に特徴をまとめます。

|特徴|説明|
|:--|:--|
|Goモジュールの概要|関連するパッケージをモジュールに収集し、他の開発者が使用できるようにモジュールを公開できる|
|Goモジュール公開の概要|・モジュールに含まれるパッケージを設計しコーディングする<BR><BR>・Go ツールを介して他のユーザーが確実に利用できるようにする規則を使用して、コードをリポジトリにコミットする<BR><BR>・開発者から見つけられるようにモジュールを作成し公開する<BR><BR>・時間の経過とともに、各バージョンの安定性と下位互換性を示すバージョン番号つき規則を使用するバージョンでモジュールを改定する|
| `go.mod`ファイルの概要|モジュールは、ルートディレクトリに`go.mod`という名前のUTF-8でエンコードされたテキストファイルにより定義される|
|`go mod tiny`|メイン モジュール内のすべてのパッケージと、それらがインポートするすべてのパッケージを再帰的にロードすることで機能する|

# 概要
## Developing and publishing modules
下記を基に整理します。

https://go.dev/doc/modules/developing

> You can collect related packages into modules, then publish the modules for other developers to use. This topic gives an overview of developing and publishing modules.

- 関連するパッケージをモジュールに収集し、他の開発者が使用できるようにモジュールを公開できる

### Workflow for developing and publishing modules
> When you want to publish your modules for others, you adopt a few conventions to make using those modules easier.

> The following high-level steps are described in more detail in Module release and versioning workflow.

> 1. Design and code the packages that the module will include.
> 1. Commit code to your repository using conventions that ensure it’s available to others via Go tools.
> 1. Publish the module to make it discoverable by developers.
> 1. Over time, revise the module with versions that use a version numbering convention that signals each version’s stability and backward compatibility.

- 他者に自分のモジュールを公開したい場合は、それらのモジュールを簡単に使用できるように、いくつかの規則を適用する
    1. モジュールに含まれるパッケージを設計しコーディングする
    1. Go ツールを介して他のユーザーが確実に利用できるようにする規則を使用して、コードをリポジトリにコミットする
    1. 開発者から見つけられるようにモジュールを作成し公開する
    1. 時間の経過とともに、各バージョンの安定性と下位互換性を示すバージョン番号つき規則を使用するバージョンでモジュールを改定する

### Design and development
> Your module will be easier for developers to find and use if the functions and packages in it form a coherent whole. When you’re designing a module’s public API, try to keep its functionality focused and discrete.

- 関数とパッケージが一貫した全体を形成している場合、開発者はモジュールを見つけられ使用することは簡単
- モジュールのパブリック API を設計するときは、その機能に焦点を当て、個別に保つようにする

> Also, designing and developing your module with backward compatibility in mind helps its users upgrade while minimizing churn to their own code. You can use certain techniques in code to avoid releasing a version that breaks backward compatibility. For more about those techniques, see Keeping your modules compatible on the Go blog.

- 下位互換性を念頭に置きモジュールを設計、開発すると、ユーザが独自のコードへの変更を最小限に抑えながらアップグレードするのに役立つ
- コード内で特定のテクニックを使用すると、下位互換性をそこなバージョンのリリースを回避できる

> Before you publish a module, you can reference it on the local file system using the replace directive. This makes it easier to write client code that calls functions in the module while the module is still in development. For more information, see “Coding against an unpublished module” in Module release and versioning workflow.

- モジュールを公開する前に、replaceディレクティブを使用してローカルファイルシステム上でモジュールを参照できる
- これによりモジュールの開発中にモジュール内の関数を呼び出すクライアントコードを簡単に作成できる


### Decentralized publishing

> In Go, you publish your module by tagging its code in your repository to make it available for other developers to use. You don’t need to push your module to a centralized service because Go tools can download your module directly from your repository (located using the module’s path, which is a URL with the scheme omitted) or from a proxy server.

- Goでは、リポジトリ内のコードにタグ付けし、モジュールを公開し、他の開発者が使用できるようにする
- Goツールはリポジトリ、プロキシサーバからモジュールを直接ダウンロードできるので、モジュールを一元化されたサービスにPushする必要はない

> After importing your package in their code, developers use Go tools (including the go get command) to download your module’s code to compile with. To support this model, you follow conventions and best practices that make it possible for Go tools (on behalf of another developer) to retrieve your module’s source from your repository. For example, Go tools use the module’s module path you specify, along with the module version number you use to tag the module for release, to locate and download the module for its users.

- パッケージをコードにインポートした後、開発者はGoツールを使用して、コンパイルに使用するモジュールのコードをダウンロードする
- このモデルをサポートするには、Goツールがリポジトリからモジュールのソースを取得するための規則とベストプラクティスに従う
    - 例）Go ツールは、指定したモジュールのモジュール パスと、リリース用のモジュールにタグを付けるために使用したモジュールのバージョン番号を使用して、モジュールを見つけてユーザーにダウンロードする

### Package discovery
> After you’ve published your module and someone has fetched it with Go tools, it will become visible on the Go package discovery site at pkg.go.dev. There, developers can search the site to find it and read its documentation.

- モジュールを公開したあと、Goツールからフェッチすると、そのモジュールは`pkg.go.dev`に表示される

> To begin using the module, a developer imports packages from the module, then runs the go get command to download its source code to compile with.

- モジュールの試用を開始するには、開発者はモジュールからパッケージをインポートし、`go get`コマンドを実行しコンパイルに使用するソースをダウンロードする

### Versioning
> As you revise and improve your module over time, you assign version numbers (based on the semantic versioning model) designed to signal each version’s stability and backward compatibility. This helps developers using your module determine when the module is stable and whether an upgrade may include significant changes in behavior. You indicate a module’s version number by tagging the module’s source in the repository with the number.

- 時間の経過とともに、モジュールの改訂、改善を行うと、各バージョンの安定性と下位互換性を示すように設計されたバージョン番号を割り当てる
- モジュールを使用する開発者が、モジュールがいつ安定するか、およびアップグレードに動作の重大な変更が含まれるかどうかを判断するのに役立つ

## go.mod files
下記を基に整理します。

https://go.dev/ref/mod#go-mod-file

> A module is defined by a UTF-8 encoded text file named go.mod in its root directory. The go.mod file is line-oriented. Each line holds a single directive, made up of a keyword followed by arguments. 

- モジュールは、ルートディレクトリに`go.mod`という名前のUTF-8でエンコードされたテキストファイルにより定義される
- `go.mod`は行指向
- 各行には、キーワードとそれに続く引数で構成される単一のディレクティブが含まれる
- サンプルは、下記の通り
    ```go
    module example.com/my/thing
    
    go 1.12
    
    require example.com/other/thing v1.0.2
    require example.com/new/thing/v2 v2.3.4
    exclude example.com/old/thing v1.2.3
    replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
    retract [v1.9.0, v1.9.5]
    
    ```
> The leading keyword can be factored out of adjacent lines to create a block, like in Go imports.

- Go のインポートと同様に、先頭のキーワードを隣接する行から取り除いてブロックを作成できる

    ```
    require (
        example.com/new/thing/v2 v2.3.4
        example.com/old/thing v1.2.3
    )
    ```

> The go.mod file is designed to be human readable and machine writable. The go command provides several subcommands that change go.mod files. For example, go get can upgrade or downgrade specific dependencies. Commands that load the module graph will automatically update go.mod when needed. go mod edit can perform low-level edits. The golang.org/x/mod/modfile package can be used by Go programs to make the same changes programmatically.

- `go.mod`ファイルは、人間が読み取り、マシンが書き込みできるように設計されている
- goコマンドは、`go.mod`ファイルを変更するいくつかのサブコマンドが提供されている
    - 例
        - go get
            - 特定の依存関係をアップグレード、またはダウングレードできる
        - go edit
            - 低レベルの編集ができる


## go mod tidy
下記を基に整理します。

https://go.dev/ref/mod#go-mod-tidy

- Usage:
    ```bash
    go mod tidy [-e] [-v] [-go=version] [-compat=version]
    ```
> go mod tidy ensures that the go.mod file matches the source code in the module. It adds any missing module requirements necessary to build the current module’s packages and dependencies, and it removes requirements on modules that don’t provide any relevant packages. It also adds any missing entries to go.sum and removes unnecessary entries.

- `go mod tidy`は、go.modファイルがモジュール内のソースコードと一致することを確認する
- 現在のモジュールのパッケージと依存関係を構築するために必要な不足しているモジュール要件が追加され、関連するパッケージを提供しないモジュールの要件が削除される
- 不足しているエントリがあれば go.sum に追加され、不要なエントリが削除される

> go mod tidy works by loading all of the packages in the main module and all of the packages they import, recursively. This includes packages imported by tests (including tests in other modules). go mod tidy acts as if all build tags are enabled, so it will consider platform-specific source files and files that require custom build tags, even if those source files wouldn’t normally be built. There is one exception: the ignore build tag is not enabled, so a file with the build constraint // +build ignore will not be considered. Note that go mod tidy will not consider packages in the main module in directories named testdata or with names that start with . or _ unless those packages are explicitly imported by other packages.

- `go mod tiny`は、メイン モジュール内のすべてのパッケージと、それらがインポートするすべてのパッケージを再帰的にロードすることで機能する
    - テストによりインポートされたパッケージが含まれる
- すべてのビルドタグが有効であるかのように動作するため、プラットフォーム固有のソースとカスタムビルドタグを必要とするファイルが、通常はビルドされないソースであっても考慮される


# 実践
## 前提
下記を使用しています。

- MacOS
- VSCode
- Go 1.23.0

## Start a module that others can use
下記を基に試します
https://go.dev/doc/tutorial/create-module

このチュートリアルでは、2 つのモジュールを作成します。
- 1 つ目は、他のライブラリまたはアプリケーションによってインポートされることを目的としたライブラリ
- 2 つ目で、最初のアプリケーションを使用する呼び出し側アプリケーションを作成します


### Start a module that others can use

Goモジュールを作成します。
- Goコードは、パッケージにグループ化され、パッケージはモジュールにグループ化される
- モジュールは、Goのバージョンや他のモジュールのセットなど、コードを実行するために必要な依存関係を指定する
- モジュールの機能を追加または改善すると、モジュールの新しいバージョンが公開される
- モジュール内の関数を呼び出すコードを作成する開発者は、実稼働環境で使用する前に、モジュールの更新されたパッケージをインポートし、新しいバージョンでテストできる

1. Goモジュールコードを配置する`greetings`ディレクトリを作成します
    ```shell
    go-tutrial $ mkdir greetings
    go-tutrial $ cd greetings 
    ```
1. `go mod init`コマンドにより、モジュールパスを指定します。（example.com/greetings）を使用します
    - モジュールを公開する場合、Goツールによりモジュールをダウンロードできるパスである必要がある。（これがコードのリポジトリになる)
    - `go mod init`コマンドにより、コードの依存関係を追跡するためのgo.modファイルを作成する
        - はじめに、ファイルには、モジュールの名前と、コードがサポートするGoバージョンが含まれている
        - 依存関係を追加すると、コードが依存するバージョンがgo.modファイルにリストされる
        - これにより、ビルドの再現性が維持され使用するバージョンを直接制御できる
    ```shell
    greetings $ go mod init example.com/greetings
    go: creating new go.mod: module example.com/greetings
    greetings $ ls 
    go.mod
    greetings $ cat go.mod 
    module example.com/greetings
    
    go 1.23.0
    greetings $ 
    ```
1. grettings.goというファイルを作成します
    ```shell
    greetings $ touch grettings.go
    greetings $ ls
    go.mod          grettings.go
    greetings $
    ```
1. 下記のコードを書きます
    - 関数に渡されたnameで挨拶する
                
    ```golang
    package greetings
    
    import "fmt"
    
    // Hello returns a greeting for the named person.
    func Hello(name string) string{
    	message := fmt.Sprintf("Hi, %v. Welcome!", name)
    	return message
    }
    ```
    - <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/fc3a19b6-f776-d0f0-fff8-a7c1cfabdb02.png" width=400 />
    
    - stringのnameを受け取る
        - 戻り値もstring
        - Goでは、名前が大文字で始まる関数は、同じパッケージにない関数から呼び出せる（Goではエクスポート名という）
        - `:=`オペレータは、1行で変数の宣言と初期化を行うショートカット
            - 右側の値を使用して変数の型を決めている
            - 省略しない書き方に直すと下記になる
            ```go
            var message string
            message = fmt.Sprintf("Hi, %v. Welcome!", name)
            ```
### Call your code from another module
ここでは、上記で作成したモジュール内のHello関数を呼び出すコードを作成します

1. `hello`ディレクトリを作成します
    ```shell
    go-tutrial $ mkdir hello
    go-tutrial $ tree
    .
    ├── greetings
    │   ├── go.mod
    │   └── grettings.go
    └── hello
    
    go-tutrial $ cd hello
    hello $ 
    ```
1. `go mod init`コマンドを実行し依存関係の追跡を有効にします
    - `example.com/hello`のモジュールパスを使用します
    ```shell
    hello $ go mod init example.com/hello
    go: creating new go.mod: module example.com/hello
    hello $ ls
    go.mod
     hello $ cat go.mod 
    module example.com/hello
    
    go 1.23.0
    hello $
    ```
1. `hello.go`ファイルを作成します
    ```shell
    hello $ touch hello.go
    hello $ ls
    go.mod          hello.go
    hello $
    ```
1. `Hello`関数を呼び出すコードを書きます
    ```golang
    package main
    
    import (
    	"fmt"
    
    	"example.com/greetings"
    )
    
    func main() {
    	// Get a greeting message and print it.
    	message := greetings.Hello("Gladys")
    	fmt.Println(message)
    }
    ```
    - mainパッケージを宣言する。（Goではアプリケーションとして実行されるコードはメインパッケージに存在する必要がある）
    - `fmt`と`example.com/greetings`パッケージをインポートすることで、コードからこれらのパッケージ内の関数にアクセスできる
        - `example.com/greetings`パッケージをインポートすることで、Hello関数にアクセスできる

1. ローカルの`example.com/greetings`を使用するように`example.com/hello`モジュールを変更します
    - 本番環境で使用する場合は、リポジトリからexample.com/greetingsモジュールを公開する
    - 現時点では、モジュールを公開してないので、ローカルファイルシステム上で`example.com/greetings`コードを見つけられるように、`example.com/hello`を調整する
    - `go mod edit`コマンドを使用して、`example.com/hello`モジュールを編集し、Goツールをそのモジュールパスからローカルディレクトリにリダイレクトする

    ```bash
    hello $ pwd
    /HOMEDIR/XXXX/YYY/go-tutrial/hello
    hello $
    hello % go mod edit -replace example.com/greetings=../greetings
    hello % cat go.mod 
    module example.com/hello
    
    go 1.23.0
    
    replace example.com/greetings => ../greetings
    ```
    - replace されました
1. `go mod tidy`コマンドを実行し、`example.com/hello`モジュールの依存関係を同期し、モジュール内で追跡されていない依存関係を追加します
    ```bash
    hello $ go mod tidy
    go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000
    hello $
    hello $ cat go.mod 
    module example.com/hello
    
    go 1.23.0
    
    replace example.com/greetings => ../greetings
    
    require example.com/greetings v0.0.0-00010101000000-000000000000
    hello $
    ```
    - greetingsディレクトリでローカルコードを見つけ、`example.com/hello`が`example.com/greetings`を必要とすることを指定するrequireディレクトリが追加される
    - モジュールパス後の数字は、擬似的なバージョン
    - 
1. `go run .`により実行します
    ```
    hello $ go run .
    Hi, Gladys. Welcome!
    hello $
    ```

# 考察
今回は、Goモジュールの作成を試してみました。モジュールを作成し、利用する際の取り込み方法まで実践できました。
引き続き、チュートリアルに基づき理解を深めていきたいと思います。


# 参考
https://qiita.com/zumax/items/040c3cce24eeb6727679

https://go.dev/doc/modules/developing

https://go.dev/ref/mod#go-mod-tidy

https://go.dev/doc/modules/managing-dependencies

https://go.dev/doc/modules/version-numbers

https://go.dev/ref/mod#go-mod-file