[Golang]クリーンアーキテクチャを用いたWebアプリ入門・パッケージ構成
GolangでWebアプリを作るとき、パッケージ構成をどうすればよいか悩むことありますよね。今回はパッケージ構成の1つの例として、クリーンアーキテクチャ(Clean Architecture)の構成をご紹介しようと思います。クリーンアーキテクチャを採用しない場合も、パッケージ構成のヒントになるのではと思います。
Webフレームワークにginやechoなど使う場合にも適用できます。
クリーンアーキテクチャ
クリーンアーキテクチャを用いた構成で気をつけることは、以下の2つです。
- 役割毎にパッケージを4層に分ける
- 依存の方向は外から内のみ
パッケージ構成
パッケージ名は円の外から順に、infrastructure、interfaces、usecase、domainの4つになります。
domain ├ product_repository.go // DB操作interfaceを定義 └ product.go // モデルの種類毎にファイルを追加 infrastructure ├ router.go // リクエストにhandlerを割り当て └ product_repository.go // DB操作interfaceを実装 interfaces └ getproduct_handler.go // レスポンスを定義 usecase └ getproduct_usecase.go // 業務ロジックを記述 main.go
コード例
getproduct_handler.goの例です。package interfaces import{ // 省略 } type GetProductHandler interface{ Handle(c gin.Context) error } type getProductHandler struct { u usecase.GetProductUsecase } func NewGetProductHandler(r domain.ProductRepository) GetProductHandler { return &getProductHandler{ u : usecase.NewGetProductUsecase(r) } } func (h *getProductHandler) Handle(c gin.Context) error { id := c.Param("id") // handlerのフィールドにあるusecaseを利用する product := h.u.GetProduct(id) return c.JSON(http.StatusOK, product) }
注意点
- interfaceで定義したメソッドを、構造体が実装していること
- メソッドのレシーバーがポインタなので、NewGetProductHandler関数はポインタを返す。Handleメソッドを実装しているのはgetProductHandlerのポインタ型であり、getProductHandlerの値型は実装していないため。戻り値の型をGetProductHandlerにしているので、値型だとコンパイルエラーになる。
- NewGetProductHandler関数はinfrastructureパッケージから呼ばれる想定なので、既にinfrastructure→interfacesの参照方向がある。更に、infrastructure/product_repository.goで定義したNewProductRepository関数をこのファイルから呼んでしまうと、interfaces→infrastructureの参照方向ができて、循環参照(コンパイルエラー)になってしまう。
なので、infrastructure/router.goでNewProductRepository関数を呼び、domain.ProductRepository型の戻り値をNewGetProductHandler関数の引数に渡す。
インターフェースを利用する
※分かりづらいですがパッケージの名称として「interfaces」、Goの仕様のインターフェースは区別してインターフェースと表記します。Goのコードでは「interface」と書くのですが、見分け辛いので。
クリーンアーキテクチャは依存を外から内に制限しています。また、Golangの仕様としても循環参照が禁止されています。usecaseからインターフェースなしでrepositoryを利用しようとすると、interfaces→usecaseとusecase→interfacesの参照が発生します。この場合、後者の依存の方向が内から外になってしまっているのに加え、参照が循環していることからますGoのコンパイルエラーとなります。このusecase→interfacesの参照を無くす(依存の方向を逆転させる)ために、インターフェースを用います。
インターフェースの使い方については、過去記事に書きました。
takaittakait.hatenablog.com悩みどころ
Repositoryインターフェース
クリーンアーキテクチャで調べると、記事によって構成が違うことがあります。
Repositoryのインターフェースは今回はdomainに置いてますが、usecaseに置いている人もいます。
domainにmodelの定義とその振る舞いを書いて、usecaseは業務ロジックという分け方が綺麗な気はします。