Goのプロジェクト構成とパッケージ

# プロジェクト構成

Goでは1つのプロジェクトのレイアウトがある。

https://github.com/golang-standards/project-layout/blob/master/README_ja.md

ただし、このレイアウトを使わないといけないという規制はない。

また、以下のGo moduleのドキュメントも参考になる。

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

## project-layoutの主なディレクトリ

以下は、[project-layout](https://github.com/golang-standards/project-layout/blob/master/README_ja.md)　のレイアウトの主なディレクトリ。

:::note warn
project-layout は、Go公式のレイアウトではないので、必ずしも守らないといけない規約ではない。
:::

* `/cmd` : アプリケーションごとにコマンドのmain.goを置く
* `/internal` : 外部に公開したくないプライベートなコードを置く
* `/pkg` : 公開したいパッケージを置く
* `/test` : 追加の外部テストとデータ(結合テストや公開APIテスト)単体テストは入れない
* `/configs` : 設定ファイル
* `/docs` : 設計ドキュメント、ユーザドキュメント
* `/examples` : アプリケーション、公開ライブラリのコード例
* `/api` : SwaggerやProtocol Buffersなどを置く
* `/web` : ウェブアプリケーション固有のアセット(静的ファイルなど)
* `/build` : パッケージングと継続的インテグレーションのファイル
* `/scripts` : 解析やインストール用のスクリプト
* `/vendor` : アプリケーションが依存しているもの(Goの依存モジュールなど)
* `/deployments` : IaaS、PaaS、システム、コンテナオーケストレーションのデプロイメント設定とテンプレート(`/deploy`とすることもある)
* `/tools` : プロジェクトをサポートするツール

:::note warn
`src`は作ってはいけない。srcを使うのはJavaのスタイルなので、Goで使用してはいけない。また、`src`は一般的すぎる。
:::

## シンプルなパッケージ

ここでは`foo`というパッケージを作成するとする。

```txt
project
├── foo.go
├── foo_test.go
└── go.mod
```

`foo.go`に以下の行を付け加える。

```go:foo.go
package foo
```

クライアントは`import "github.com/user_name/foo"`とすると、使用することができる。

以下のように複数のファイルに分けることはできるが、基本的にはすべて同じパッケージである。

```txt
project
├── foo.go
├── foo_test.go
├── hoge.go
├── hoge_test.go
├── fuga.go
├── fuga_test.go
└── go.mod
```

## シンプルなコマンド

`foo`というコマンドを作成するとする。

```txt
project
├── auth.go
├── auth_test.go
├── client.go
├── main.go
└── go.mod
```

go modに、`module github.com/user_name/foo`を記載する。

以下のコマンドでクライアントはインストールできる。

```sh
go install github.com/user_name/foo@latest
```

ちなみに、1つのファイルを複数のgoに分けたときは、`go run main.go`を使ってはいけない。

https://qiita.com/twrcd1227/items/00c2802a2131c849fa5c

## APIサーバ

APIサーバでは外部に公開するパッケージはないので、すべて`internal/`における。

```txt
project/
├── cmd/
│   ├── api-server/
│   │   └── main.go
│   └── metrics-analyzer/
│       └── main.go
├── internal/
│   ├── auth/
│       └──・・・
│   └──metrics/
│       └──・・・
└── go.mod
```

# パッケージ

パッケージのわけ方はレイヤーごとに分けたり、ドメインごとに分けたりする方法があるが、決まりはない。1つのパッケージの中のファイルが多すぎたり、少なすぎたりしてはいけない。適切な粒度にする。

## ユーティリティパッケージ

**utils**, **common**, **base**, **shared** といった共有パッケージは作成してはいけない。

```go
package util

func NewStringSet(...string) map[string]struct{} { return nil }
func SortStringSet(map[string]struct{}) []string { return nil }
```

```go:クライアント側
set := util.NewStringSet("c", "a", "b")
fmt.Println(util.SortStringSet(set)
```

これをリファクタして、`stringset` というパッケージを作成する

```go
package stringset

func New(...string) Set { return nil }
func Sort(map[string]struct{}) []string { return nil }
```

```go:クライアント側
set := stringset.New("c", "a", "b")
fmt.Println(stringset.Sort(set))
```

さらに struct を作成して、関数をメソッドにする。

```go
package stringset

type Set map[string]struct{}
func New(...string) Set { return nil }
func (s Set) Sort() []string { return nil }
```

```go:クライアント側
set := stringset.New("c", "a", "b")
fmt.Println(set.Sort())
```

意味のないパッケージ名はつけてはいけない。
また、パッケージの名前は何を含むかではなく、何を提供するかで決める。

## パッケージ名の衝突

例えば、`foo`というパッケージをインポートして、`foo`という変数を作成すると、名前がコンフリクトしてしまう。

```go
import foo

func main() {
    foo := foo.New()
}
```

`foo`という変数を宣言した後はfooパッケージにアクセスすることはできない。

異なる名前をつける以外の方法として、エイリアスを使う方法がある。

```go
import pkgfoo "foo"

func main() {
    foo := pkgfoo.New()
}
```

また、パッケージ以外にも、組み込み関数と変数の名前の衝突も避ける。(`copy()`など)

## スライド

<script defer class="speakerdeck-embed" data-id="0ec2abb7fc414fcb861bec0244abb3ec" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

## 参考文献

1. [golang-standards/project-layout](https://github.com/golang-standards/project-layout/blob/master/README_ja.md)
1. [Organizing a Go module - The Go Programming Language](https://go.dev/doc/modules/layout)
1. [Japanese Version - 100 Go Mistakes and How to Avoid Them](https://100go.co/ja/)