アプリケーションアーキテクチャ設計に関する覚書(Go)
1. 前提条件
- REST APIを提供するWEBアプリケーション
- grpcのインターフェイスを提供する場合でも大体同じ
2. 基本的な考え方
- ドメイン知識はドメイン層に集約
- フレームワークへの依存を極力避ける
- クリーンアーキテクチャにはこだわらない
3. ディレクトリ構造
.
├── config
├── handler
├── lib
├── migrate
├── model
├── repository
├── usecase
├── validator
├── main.go
├── go.mod
└── go.sum
4. 各モジュールの役割
config
- アプリケーションの設定を管理
- 典型的にはconfig/config.goだけが配置される
-
.env
の情報をロードする
package config
type Config struct {
...
}
handler
- MVCにおけるコントローラ
- HTTPリクエストの受信とレスポンスの返却を担当する
- HTTPリクエストに関する処理はこのレイヤーで完結させ、これ以降のレイヤーに影響させない
-
handler
で使用するリクエスト/レスポンスの構造体もここに配置する
lib
- フレームワークのミドルウェアやラッパーなどが配置される
- 雑多なモジュールが作られるディレクトリなので、責務過剰にならないよう注意する必要がある
model
repository
- データアクセスを抽象化するレイヤー
- DBアクセスや外部APIアクセスを実行するモジュールが配置される
usecase
- ドメインモデルを用いたビジネスロジックが記述されるレイヤー
-
handler
から呼び出される
- 原則としてユースケースからユースケースを呼び出してならない
- ユースケース間で共有したいロジックはドメイン層に移動させるか、共有ロジックとして独立させる
- 基本的なモジュール構造は以下の通り
type ShowExampleUseCase interface {
Execute() error
}
func NewShowExampleUseCase(arg int) ShowExampleUseCase {
return &showExampleUseCase{
value: arg,
}
}
type showExampleUseCase struct {
value: int
}
func (u *showExampleUseCase) Execute() error {
...
}
- ↑Usecaseパッケージ外から呼び出されるinterfaceとその実装、ファクトリー関数によって構成される
- 1つのインターフェイスは1つのメソッドしか持たず、ユースケースの数だけ上記のセットが作成される
- 定義するモジュールの数が増える分、煩雑になるが、責務が明確になることを優先している
validator
- ドメインルールのバリデーションを行うモジュールが配置される
- modelの中に含めてもよいかもしれない
-
handler
で行うバリデーションはフォーマットチェックで、usecase
で実行されるドメインルールに対するバリデーションを行う
- 例えば、DBにアクセスしてデータの存在チェックを行ったりするかもしれない
main.go
- プログラムのエントリーポイント
- アプリケーションの初期設定やモジュールの構築を行う
- 必要に応じて
cmd
ディレクトリを作成し、その中でserver
用cli
用のmain.go
を作成してもよい
5. ファクトリーとinterface
- テストを容易にする為に別レイヤーから呼び出されるモジュールは
interface
を定義する
- 具体的には
usecase
とrepository
は必ずinterface
を定義する