Taka blog

プログラミングとか

[Golang]クリーンアーキテクチャを用いたWebアプリ入門・パッケージ構成

GolangでWebアプリを作るとき、パッケージ構成をどうすればよいか悩むことありますよね。今回はパッケージ構成の1つの例として、クリーンアーキテクチャ(Clean Architecture)の構成をご紹介しようと思います。クリーンアーキテクチャを採用しない場合も、パッケージ構成のヒントになるのではと思います。

Webフレームワークにginやechoなど使う場合にも適用できます。

クリーンアーキテクチャ

f:id:masquerade0:20211229114201j:plain

クリーンアーキテクチャを用いた構成で気をつけることは、以下の2つです。

  1. 役割毎にパッケージを4層に分ける
  2. 依存の方向は外から内のみ




パッケージ構成

パッケージ名は円の外から順に、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は業務ロジックという分け方が綺麗な気はします。