1
3

プロジェクト構成

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

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

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

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

以下は、project-layout のレイアウトの主なディレクトリ。

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 : プロジェクトをサポートするツール

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

シンプルなパッケージ

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

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

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

foo.go
package foo

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

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

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

シンプルなコマンド

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

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

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

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

go install github.com/user_name/foo@latest

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

APIサーバ

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

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

パッケージ

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

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

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

package util

func NewStringSet(...string) map[string]struct{} { return nil }
func SortStringSet(map[string]struct{}) []string { return nil }
クライアント側
set := util.NewStringSet("c", "a", "b")
fmt.Println(util.SortStringSet(set)

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

package stringset

func New(...string) Set { return nil }
func Sort(map[string]struct{}) []string { return nil }
クライアント側
set := stringset.New("c", "a", "b")
fmt.Println(stringset.Sort(set))

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

package stringset

type Set map[string]struct{}
func New(...string) Set { return nil }
func (s Set) Sort() []string { return nil }
クライアント側
set := stringset.New("c", "a", "b")
fmt.Println(set.Sort())

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

パッケージ名の衝突

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

import foo

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

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

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

import pkgfoo "foo"

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

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

スライド

参考文献

  1. golang-standards/project-layout
  2. Organizing a Go module - The Go Programming Language
  3. Japanese Version - 100 Go Mistakes and How to Avoid Them
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3