🐙
クリーンアーキテクチャの依存逆転の原則
コードレビュー時に指摘したところについて備忘録のため記述。
事象
Go、DDD、クリーンアーキテクチャを採用してAPI開発を行っている。ディレクトリ構成は簡略化しているが、以下のような構成。
.
└── my_project/
└── internal/
├── domain
├── infra/
│ ├── gateway
│ └── db
├── usecase
└── handler
外部APIを実行しデータを取得する処理で以下のようなPRをレビューした。
infra/gateway/api.go
package gateway
import (
"context"
)
type ApiMockClient struct {
}
func NewApiMockClient() *ApiMockClient {
return &ApiMockClient{}
}
func (g *ApiClient) GetData(ctx context.Context) (*datatype, error) {
// 外部APIを実行しデータを取得する処理のモック
return data, nil
}
infra/gateway/api_client.go
package gateway
import (
"context"
)
type ApiClient interface {
GetData(ctx context.Context) (datatype, error)
}
usecase/get_data.go
package usecase
import (
"context"
"my_project/infra/gateway"
)
type GetDataUseCase struct {
getDataGateway gateway.ApiClient
}
func NewGetDataUseCase(getDataGateway gateway.ApiClient) GetDataUseCase {
return GetDataUseCase{
getDataGateway: getDataGateway
}
}
func (u GetDataUseCase)Execute(ctx context.Context) (*datatype, error) {
// 外部APIを使用したデータ取得処理
return data, nil
}
違和感を感じた点
外部APIのinterfaceがinfra/gateway配下に記載されている。
クリーンアーキテクチャの原則では、内側のレイヤー(usecase)から外側のレイヤー(infra)への依存は避けるべきとされていて、infra配下にこのinterfaceを置いてしまうと、usecase側でinfra/gatewayをimportしなくてはいけなくなり、usecaseがinfraに依存してしまうのではないかと考えた。
そこで、usecase/gatewayにinterfaceを配置するよう改善提案をした。
.
└── my_project/
└── internal/
├── domain
├── infra/
│ ├── gateway
│ └── db
├── usecase/
│ └── gateway/
│ └── api_client.go
└── handler
まとめ
- クリーンアーキテクチャにおいては、依存関係の方向性に注意する必要がある。
- 実装(外部API呼び出しなど)は infra 側で行い、usecase はあくまで抽象に依存するよう設計する。
- interfaceは使う側に置く
補足
疑問
domain/serviceで外部APIを使用したい場合は、domain層にinterfaceを用意するのか??
自分なりの結論
外部依存(API呼び出しやDBアクセスなど)を含む処理は、domain層に持ち込まず、usecase層で扱って、domain/serviceに渡すのが良さそう。domainはあくまで純粋なドメインロジックのみにとどめる。
Discussion