Job Bus(旧Command Bus)

Laravel v5.0で追加された、Command Busは、v5.1でQueueと統合されて、Job Busに変更されました。今回はこのJob Busを試してみます。

Laravel 5.1では非常に残念なことに、マニュアルにQueueの説明はあるのですが、Job Busの記述がありません。Command Busパターンとしての使い方が抹消されてしまいました。ララ帳ではCommand Busパターンに注目してるので、引き続きJob Busとして掲載します。


お題

Job_bus_layer

Laravel 5.1に標準装備されている、ユーザー登録処理はAuthControllerに実装されています。コントローラーはドメイン駆動設計として見ると、プレゼンテーション層にあたります。ここにビジネスロジックが実装されていると、コントローラの中でしかロジックが使えず、再利用ができません。例えば、ユーザーを登録するコンソールコマンドを作ろうとした場合、そのロジックを再利用できません。そこで、ユーザー登録処理をサービス層にあたるJobクラスに抽出して、Job Busを経由して実行するようリファクタリングします。


事前準備


Jobの作成

artisan を使って、Jobクラスを作成します。

php artisan make:job RegisterUser

app/Jobs/RegisterUser.php が生成されるので、以下のように編集します。

<?php // app/Jobs/RegisterUser.php

namespace App\Jobs;

use App\Jobs\Job;
use App\User;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Validation\ValidationException;

class RegisterUser extends Job implements SelfHandling
{
    private $data = [];

    public function __construct($name, $email, $password, $password_confirmation)
    {
        // Jobに必要なデータを受け取る
        $this->data['name'] = $name;
        $this->data['email'] = $email;
        $this->data['password'] = $password;
        $this->data['password_confirmation'] = $password_confirmation;
    }

    public function handle() // Job Busからコールされるメソッド
    {
        // バリデーションチェック
        $validator = $this->validator();
        if ($validator->fails()) {
            throw new ValidationException($validator); // 例外をスロー
        }

        // ユーザー登録
        $this->register();
    }

    protected function validator()
    {
        return \Validator::make($this->data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }

    protected function register()
    {
        return User::create([
            'name' => $this->data['name'],
            'email' => $this->data['email'],
            'password' => bcrypt($this->data['password']),
        ]);
    }
}

コントローラでJobを実行

Job_bus_flow

AuthController.php の postRegister() メソッドをオーバーライドしてJob Busを経由してJobを実行します。AuthController.php を以下のように修正します。

<?php // app/Http/Controllers/Auth/AuthController.php

namespace App\Http\Controllers\Auth;

use App\User;
use Illuminate\Http\Request;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

use App\Jobs\RegisterUser;
use Illuminate\Contracts\Validation\ValidationException;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers;

    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);
    }

/* 削除
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }
*/

/* 削除
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
*/

    // ① オーバーライドして修正
    public function postRegister(Request $request)
    {
        try {
            $this->dispatch( // ②
                new RegisterUser( // ③
                    $request['name'],
                    $request['email'],
                    $request['password'],
                    $request['password_confirmation']
                )
            );
        } catch (ValidationException $e) { // ④
            $this->throwValidationException(
                $request, $e->getMessageProvider()
            );
        }

        // ⑤
        \Flush::success('ユーザー登録が完了しました。ログインしてください。');

        return redirect('auth/login');
    }
}

① AuthenticatesAndRegistersUsersトレイトの中で定義されている、postRegister()をオーバーライドして修正します。

② dispatch() メソッドを使って、Job Bus経由でJobを実行します。
コントローラは Illuminate\Foundation\Bus\DispatchesJobsトレイトを使用しています。DispatchesJobsトレイトが Job Busの実態で、dispatch()メソッドを実装しています。

ここで、Jobクラスの handle() を直接コールすることも出来るのですが、Job Busを経由させることで、Jobをキューに投入することもできますので、この仕組に従います。

③ Jobクラスを生成して、Requestから渡されてきた、入力データを引き渡します。

④ エラーハンドリングを行います。

laracasts/flashパッケージを使っています。


動作確認

以下のURLからユーザー登録を行います。

http://localhost:8000/auth/register

register

登録後にログイン画面が表示され、「ユーザー登録が完了しました。ログインしてください。」と表示されれば成功です。
エラーデータを入力してバリデーションチェックが機能しているかも確認してください。


リクエストをJobクラスのコンストラクタにマッピング

リクエストをJobクラスのコンストラクタの引数にマッピングする便利な方法があります。AuthController.php を以下のように修正します。

<?php // app/Http/Controllers/Auth/AuthController.php

// ...

class AuthController extends Controller {
    // ...

    public function postRegister(Request $request)
    {
        try {
            $this->dispatch(
                // ① リクエストをコマンドクラスのコンストラクタにマッピングして実行
                $this->dispatchFrom(RegisterUser::class, $request);
            );
        } catch (ValidationException $e) {
            // ...
        }
        // ...
    }
}

① リクエストをJobクラスのコンストラクタにマッピングして実行します。
先ほどは複数行にわたって、手動でリクエストをコンストラクタにマッピングしていましたが、dispatchFrom() を使うことで1行で済んでしまいました。超クールです。

※ただし、この機能を利用するにはリクエストのパラメータ名とコンストラクタの引数名を合わせておく必要があります。


まとめ

ユーザー登録のロジックをプレゼンテーション層であるコントローラから、サービス層であるJobクラスに抽出するリファクタリングが完了しました。Job Busを利用したことで、少し構造は複雑になりましたが、以下のメリットが得られます。

Jobクラス/Job Bus のメリット

  • ビジネスロジックをコントローラから除外し、再利用可能にできる。
  • Jobクラス名がアプリケーションに行ってほしい処理の指示(コマンド)になるので、何をしているかがコードを見て分かりやすい。(コードに意図が残る)
  • Jobクラスはユースケースと1対1で対応するので、設計と実装を見比べやすい。

コンソールコマンドからJob Busを使って、Jobクラスを利用する例は、Job Bus:コンソールコマンド編をご覧ください。

Jobクラスの生成方法、dispatchFrom()の使い方、Jobをキューにする方法は公式サイトをご覧ください。
http://laravel.com/docs/5.1/queues


所感

コマンドバスパターンを初めて見た時、上手く使えば、ウェブアプリを設計する上でも、メンテナンスする上でも非常に有用なのではないかと、直感的に感じました。しかし、それと同時にどの様にJob Busを使うべきか、まだ自信が持てていません。サービスクラスを自作して、それをコールするのと、Jobクラスを利用するのと何が違うのか?Jobクラスを作っていくと、単機能のクラスがものすごく沢山できてしまうのでは?等々。もっと、コマンドバスパターンが出てきた背景や使い方の情報収集と実際に使ってみる経験が必要だと思っています。

Job Bus(旧Command Bus)」への2件のフィードバック

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中