クリーンアーキテクチャを学んでいると、UseCaseという名前ではなく Interactor という少し見慣れないオブジェクトに出会うことがある。この記事では、Interactorがなぜ使われるのか、その役割とメリットを解説する。
この記事のギモン
なぜusecase.NewCreateAccountUseCaseではなくusecase.NewCreateAccountInteractorなのか?Interactorの正体とは?
なぜInteractorが必要なのか?
結論から言うと、Interactorは仲介役という単語自体の意味から予想される通り、 「UseCaseの具体的な実装を担当する仲介役」 である。
クリーンアーキテクチャでは、関心事の分離が重要視される。特に、アプリケーションの核となるビジネスルールを定義するUseCaseは、できるだけシンプルに保ちたいという思想がある。
理想的なUseCaseのインターフェースは、次のように「何をするか」だけを定義する。
// 「アカウントを作成する」というビジネスルールそのものを定義
type CreateAccountUseCase interface {
Execute(context.Context, CreateAccountInput) (CreateAccountOutput, error)
}
このUseCaseは「実行(Execute)できる」ことだけが分かればよく、余計な情報を持つべきではない。
しかし、実際の処理ではデータベースとやり取りするためのリポジトリや、結果を整形して返すためのプレゼンターといった依存オブジェクトが不可欠だ。
ここでInteractorが登場する。
Interactorは、これらの依存オブジェクトをすべて保持し、UseCaseインターフェースを実装することで、 ビジネスルールの純粋さを保ちつつ、具体的な処理を実現する という役割を担う。
ドメインオブジェクト,リポジトリ,プレゼンターを使用してUseCaseインターフェースを実装している様子が以下の図からよくわかる。

コードで見るInteractorの仕組み
Interactorがどのように機能するのか、コードを追いながら見ていこう。
1. Interactorの構造
Interactorは、処理に必要な依存オブジェクトをフィールドとして保持するstruct(構造体)として定義される。
// 依存オブジェクト(リポジトリ、プレゼンター等)を保持する仲介役
type createAccountInteractor struct {
repo domain.AccountRepository // DB操作を担当
presenter CreateAccountPresenter // 出力整形を担当
ctxTimeout time.Duration
}
2. UseCaseインターフェースの実装
次に、このInteractorがUseCaseインターフェースのExecuteメソッドを実装する。これにより、InteractorはUseCaseとして振る舞えるようになる。
// createAccountInteractorがExecuteメソッドを持つ
func (a createAccountInteractor) Execute(ctx context.Context, input CreateAccountInput) (CreateAccountOutput, error) {
// ... アカウント作成のロジック ...
// 保持しているリポジトリを使ってDBに保存
account, err := a.repo.Create(ctx, account)
if err != nil {
return a.presenter.Output(domain.Account{}), err
}
// 保持しているプレゼンターを使って出力
return a.presenter.Output(account), nil
}
ポイントは、Interactorが内部にrepoやpresenterを持っているため、Executeメソッド内でそれらを利用できる点だ。
3. Interactorの生成と利用
最後に、Interactorを生成する関数だ。この関数は、必要な依存関係(repoやpresenter)を受け取り、Interactorのインスタンスを生成して返す。
重要なのは、戻り値の型がcreateAccountInteractorという具体的な型ではなく、CreateAccountUseCaseというインターフェースになっている点だ。
// Interactorを生成し、「UseCase」として返す
func NewCreateAccountInteractor(
repo domain.AccountRepository,
presenter CreateAccountPresenter,
t time.Duration,
) CreateAccountUseCase { // 戻り値はインターフェース型
return createAccountInteractor{
repo: repo,
presenter: presenter,
ctxTimeout: t,
}
}
これにより、呼び出し側はInteractorという具体的な実装を意識することなく、「CreateAccountUseCaseというビジネスルールを実行するオブジェクト」として扱うことができる。
結論
Interactorは、クリーンアーキテクチャにおいて非常に重要な役割を果たす。
-
UseCase(インターフェース)- 役割: アプリケーションのビジネスルール(契約)を定義する。
- 特徴: 「何をするか」だけを規定し、シンプルに保つ。
-
Interactor(構造体)-
役割:
UseCaseインターフェースの具体的な実装。リポジトリやプレゼンターといった依存関係をまとめる「仲介役」であり「実行役」。 - 特徴: 「どうやってやるか」を担当する。
-
役割:
このように役割を分離することで、ビジネスロジックと具体的な実装の詳細が切り離され、変更に強く、テストしやすい、クリーンなアーキテクチャが実現できるのだ。
参考リポジトリ