Goのディレクトリ構成、どうしてる? Java脳で考える最小構成
シンプルに管理したい!
おつかれさまです。
GoでAPI開発をして数か月が経ちました。
最近、悩んでいるのが「ディレクトリ構成」でございます。
わたくしJava出身でして、Javaでは多くのWebアプリケーションがMVCアーキテクチャに基づいて構成されてるんですよ。フレームワークもそれを前提に設計されています。
MVCのような明確なアーキテクチャパターンがあれば(だいぶテンプレート化されているので)まあまあ馴染みがあるのですが、Goの『シンプルで柔軟なスタイル』って難しくないですか?
柔軟というか曖昧というか。どう整理すべきか迷うところ。
この記事では、Java出身のエンジニアが「これはしっくり来る!」と感じたディレクトリ構成と、その背景・工夫について共有します。
複雑なプロジェクトではなく、Ginを使ってサーバーを起動し、簡単なAPI通信とリクエスト処理を行うというケースです。
あくまで初学者の立場から書いている内容ですので、ご参考程度にご覧ください。
(ディレクトリ構成については、やはり現場の実例を見て学ぶのが一番確実だと思います。)
📁 最終的に落ち着いた構成
├── cmd/
│ └── myapp/
│ └── main.go ← エントリーポイント
├── internal/
│ ├── handler/ ← HTTPハンドラー
│ │ └── auth.go
│ ├── middleware/ ← GinのCORSミドルウェアなど
│ │ └── cors.go
│ ├── repository/ ← DBアクセス
│ │ └── user_repository.go
│ ├── model/ ← リクエスト・レスポンス構造体など
│ │ └── login.go
│ ├── db/
│ │ └── db.go ← DB初期化(Init)
│ └── router/
│ └── router.go ← Ginエンジン・ルーティング設定
├── go.mod
├── go.sum
📝 簡単な構成の説明
ディレクトリ | 役割 |
---|---|
cmd/myapp/ |
実行エントリーポイント(main.go ) |
internal/handler/ |
HTTPハンドラー(エンドポイントごとの処理) |
internal/middleware/ |
GinのCORSなど共通処理 |
internal/repository/ |
DBへのCRUD処理(ORMやSQLなど) |
internal/model/ |
リクエスト・レスポンスの構造体 |
internal/db/ |
DBの初期化(接続確立) |
internal/router/ |
ルーティング(Ginの設定) |
💡 工夫したポイント
internal/
ディレクトリを使ってパッケージのカプセル化
1. こちらは大勢が知ってる基本のキ。
Goでは internal/
配下にあるパッケージは、同一モジュール内からしかimportできません。
他のアプリからの誤ったimportを防げるため、安全かつクリーンに保てます。
Javaで言うところの「protected
」や「モジュールバウンダリ」に近い考え方ですね。
Goの公式リポジトリ構成(Standard Layout) でも使っていたので、これは外せないなぁと思いそのまま使いました。
むしろ本題はここからで、 internal/
配下の編成はどうする? というのが悩みのタネなんですよね…
2. ディレクトリを「役割ごと」に分割
- Javaでよく見る「
controller
,service
,repository
」構成に近づけながら、 - Goの「シンプルで読みやすい」流儀も意識しています。
具体的には:
- ハンドラーは
handler/
に、ビジネス処理はusecase/
に分けることも可能 - DB処理は
repository/
にまとめ、構造体はmodel/
に独立させる - CORSなど共通処理は
middleware/
に置く
などなど、まとめることでソースが追いやすくなるかなぁと思いました。
cmd/myapp/main.go
をエントリーポイントにした理由
3. 複数のアプリケーションを持つプロジェクトでも柔軟に対応できるよう、
cmd/
にアプリ単位で main パッケージを配置する構成を採用しました。
こちらも Goの公式リポジトリ構成(Standard Layout) に準拠しています。
ま、最初はルートに入れちゃってもいい気がしますね!
(mainに限ってはすべて package main
で宣言しますし)
✨ 以上でございます。
この構成は「最低限・シンプル・わかりやすい」を意識しており、
API中心の小〜中規模サービスには非常にフィットします。
必要に応じて usecase/
, service/
, domain/
を追加し、レイヤーを厚くしていくのも簡単ですし、とりあえずこのまま進めていこうと思います。
今回ご紹介した構成が正解というわけではありませんので、
「いやいやこっちのほうがいいよ!こういう理由があるからね!」などご意見ございましたら、ぜひぜひご教示くださいませ!
お読みいただきありがとうございます。
🎁 おまけ:スライスの命名について
Java出身の方へ。(あと自分用メモでもあります)
var users []User
Javaではつい userList
や userArray
などと命名したくなりますが、
Goでは 複数形(users
)を使うのが一般的で自然だそうです。
例えば:
func GetAllUsers() ([]User, error) {
// DBから取得したusersを返す
return users, nil
}
Goでは「明快でシンプルな名前」を好みます。
List
, Array
, Map
といった装飾語はなるべく避けるスタイルが多いみたいです。
Discussion
自分も同じような構成でやっているので、同じ考え方を持っている人に出会えてすごく嬉しいです。Goってパッケージ名をシンプルにしましょうって考え方があるのに複雑にしようとする人たちがいるので、そういうプロジェクトはハズレだなと感じているこの頃です。
2について
DBは
infra/db
とかinfra/postgresql
などもありかなって思いました。こうすることで、ディレクトリ増えすぎる問題、パス深すぎる問題の対処ができるかなって思って実践しています。おまけについて
最後のスライスの命名に関して自分は、「複数形があるもの」についてはこの方法で、Statusなどの複数形がないと言われているものはListをSuffixにしますね。
わたなおさん、こんにちは!
コメントありがとうございます。
実体験ベースのコメント、非常に有り難いです。
こちらは複数のDBを扱うなら〜ってことでしょうか?
このフォルダ配下ってinitメソッドだけ(初期化だけ)を実装している状況なのですが、他の処理って何か実装されてますか?
今のところ、私はひとつのDB(PostgreSQL)しか扱っていなくて、とりあえずこの辺りでいいかぁと今後のことを考えずに配置している状況でございます…
Listも使っていいんですか!
てっきりList絶許の文化言語なのかと思っておりました☀
sだとしっくり来ないものもあったので、モヤモヤしていて…ありがとうございます!
返信ありがとうございます。
こちらは複数のDBを扱うなら〜ってことでしょうか?
複数のDBを扱うなら〜というのもあるのですが、将来的にどうなるかわからないという理由と依存の方向をわかりやすくするって目的もあります。ディレクトリ増えすぎる問題、パス深すぎる問題の対処がかなり大きいですが、この辺りも理由になってますね。
ListのSuffixについて
正直プロジェクト依存ですが、Statusesってキモいなーってなりますよね。なので、自分が参画したプロジェクトではよろしくないけど我慢しましょうってことにしてます。
なるほど〜✨️
そうそうそう、Statusesとかキモいんですよ!
諸々ありがとうございます。
またご意見いただけたら嬉しいですー!