🐙

クリーンアーキテクチャの依存逆転の原則

に公開

コードレビュー時に指摘したところについて備忘録のため記述。

事象

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