0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

運用したUseCaseクラスの設計思想とメリット

Last updated at Posted at 2025-12-10

はじめに

ビジネスロジックをどこに配置するかは永遠のテーマだと思います。
今回のプロジェクトでは、1クラスで1個の事しかしないUseCaseクラスを採用することにしました。
「1個の事」を何のまとまりとするかは難しいのですが、そこに関しては各々に任せています。
業務上の1つのまとまった操作を原則で考えてます。

ルール

  • 配置場所: app/UseCases/<ドメイン名>/
  • クラス名: 動詞で始まり「Action」で終わる
  • メソッド: publicメソッドはhandle()のみ
    • privateメソッドは複数実装可
  • 引数が複雑な場合はDTOも活用する
活用例
$action = new CreatePostAction($huga,$hoge);
$action->handle();

作ったもの

インターフェース

UseCaseの呼び出し方を統一したかったので、インターフェースを作成。

app/UseCases/UseCaseHandler.php

namespace App\UseCases;

interface UseCaseHandler
{
    public function handle();
}

UseCase作成コマンド

人間の手で作成した頃はこのコマンドを叩いて使っていましたが、AI (自分の場合Claude Code)を活用するようになってからはClaude Codeのカスタムコマンドに組み込んでいます。

PostCreateAction を作成
$ php artisan make:usecase Post/PostCreate
app/Console/Commands/MakeUseCaseCommand.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;

class MakeUseCaseCommand extends Command
{
    // コマンドのシグネチャと説明
    protected $signature = 'make:usecase {name}';

    protected $description = 'Create a new use case class with a handle method';

    protected $files;

    public function __construct(Filesystem $files)
    {
        parent::__construct();
        $this->files = $files;
    }

    public function handle()
    {
        // UseCaseInterface の存在チェックと生成
        $this->createUseCaseInterfaceIfNotExists();

        // 引数からディレクトリとクラス名を取得(例: Customer/DeleteCustomer)
        $name = $this->argument('name');
        $parts = explode('/', $name);
        $classBase = array_pop($parts);
        $className = $classBase.'Action';

        // app/UseCases 以下のパスを生成
        $directoryPath = app_path('UseCases/'.implode('/', $parts));
        $fullPath = $directoryPath.'/'.$className.'.php';

        // 同一ファイルが存在する場合はエラー
        if ($this->files->exists($fullPath)) {
            $this->error("UseCase already exists at {$fullPath}!");

            return;
        }

        // ディレクトリが存在しなければ作成
        if (! $this->files->isDirectory($directoryPath)) {
            $this->files->makeDirectory($directoryPath, 0755, true);
        }

        // 名前空間の組み立て(例: App\UseCases\Customer)
        $namespace = 'App\\UseCases';
        if (! empty($parts)) {
            $namespace .= '\\'.implode('\\', $parts);
        }

        // スタブ(テンプレート)の取得と置換
        $stub = $this->getStub();
        $stub = str_replace('{{ namespace }}', $namespace, $stub);
        $stub = str_replace('{{ class }}', $className, $stub);

        // ファイルの生成
        $this->files->put($fullPath, $stub);

        $this->info("UseCase created successfully at {$fullPath}");
    }

    /**
     * UseCaseInterface が存在しなければ生成する
     */
    protected function createUseCaseInterfaceIfNotExists()
    {
        $interfacePath = app_path('UseCases/UseCaseHandler.php');

        if (! $this->files->exists($interfacePath)) {
            // ディレクトリがなければ作成
            $interfaceDir = dirname($interfacePath);
            if (! $this->files->isDirectory($interfaceDir)) {
                $this->files->makeDirectory($interfaceDir, 0755, true);
            }

            $interfaceStub = <<<'STUB'
<?php

namespace App\UseCases;

interface UseCaseHandler
{
    public function handle();
}
STUB;
            $this->files->put($interfacePath, $interfaceStub);
            $this->info("UseCaseInterface created successfully at {$interfacePath}");
        }
    }

    /**
     * UseCase のスタブテンプレート
     *
     * @return string
     */
    protected function getStub()
    {
        return <<<'STUB'
<?php

namespace {{ namespace }};

use App\UseCases\UseCaseHandler;

/**
 * TODO: どういうユースケースなのか記載
 */
final class {{ class }} implements UseCaseHandler
{
    public function __construct()
    {
    
    }
    
    public function handle()
    {
        // TODO: ロジックを書くます
    }
}
STUB;
    }
}

1年程運用し感じたメリット

人間から見て

  • ドメインごとに整理され、クラス名が動詞で始まるため、目的の処理を見つけやすい
  • 1クラス1責務なので、無関係なメソッドが視界に入らず、コードレビューがしやすい

AI活用開発

  • UseCase内に処理が閉じているため、AIが余計な箇所を変更してしまうリスクがない
  • 数千行のServiceクラスと比べて文脈を理解しやすく、意図通りの修正が得られやすい
  • ファイルサイズが小さいため、コンテキストを圧迫せず、複数ファイルを同時に扱える
  • 影響範囲が明確で、意図しない変更を防げる(大きなクラスでコードを勝手に削除された経験あり)

おまけ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?