Help us understand the problem. What is going on with this article?

ビジネスロジックが複雑なときはRequestオブジェクトを作るといいんじゃないかというお話

More than 1 year has passed since last update.

この記事は Relux AdventCalendar 6日目の記事です。

Requestレイヤーを導入した話

Webアプリケーションのソースコードは、リクエストデータの妥当性チェックとデータ整合性チェックとデータ整形などなど、往々にして長くなりがちです。ビジネスロジックが複雑になっていくほど、データ整合性のチェックや、外部サービス連携のためのデータ整形の処理が膨らんでいきます。

そこでリクエストデータを精査してアプリケーションに使いやすい形で渡す役割として、Requestオブジェクトというレイヤーを導入してみました。

Requestレイヤーの役割

リクエストデータのバリデーション

フォームからPOSTされた値の「必須項目」や「値型」のバリデーションだけでなく、DBのデータを参照してビジネス要件的に妥当なリクエストかどうかの確認もRequestオブジェクトが行います。

# ReservationRequest.php

class ReservationRequest extends Request
{
    public $validate = [
        'plan_id' => [
            'notEmpty' => [
                'rule' => ['notEmpty'],
            ],
            'exists' => [
                'rule' => ['planExists'],
            ],
            'inSalesPeriod' => [
                'rule' => ['inSalesPeriod'],
            ],
        ],
    ];

    /**
     * 宿泊プランが存在するかチェックする  
     */
    public function planExists()
    {
        return $this->plan() ? true : false;
    }

    /**
     * 宿泊プランが利用可能な日程かチェックする
     */
    public function inSalesPeriod()
    {
        $plan = $this->plan();
        $schedule = $this->schedule();
        return $plan->inSalesPeriod($schedule);
    }

    /**
     * 宿泊プランを返す
     */
    private function plan()
    {
        if (!$this->hasCache('plan')) {
            $model = ClassRegistry::init('Plan');
            $plan = $model->findById($this->get('plan_id'));
            $this->setCache('plan', $plan ? $plan : null);
        }
        return $this->getCache('plan');
    }

    /**
     * 宿泊日程オブジェクトを返す
     */
    private function schedule()
    {
        return new Schedule(
            new DateTimeImmutable($this->get('checkin')),
            new DateTimeImmutable($this->get('checkout'))
        );
    }

この擬似コードでは予約フォームからリクエストされたプランIDとチェックイン日・チェックアウト日が実際に予約できるかまでバリデーションをかけています。

リクエストデータの整形

カスタマーにできるだけストレスを感じずに予約してもらえるようにある程度の入力ミスを許容して、サーバー側で変換することも大切です。

例えば電話番号を全角数字で入力してしまった場合に、半角数字に変換して後続処理に回すということは良くやると思いますが、これもRequestクラスが行います。

コントローラーで処理するオブジェクトを生成して返す

Requestオブジェクトを実装した当初は、整形したリクエストデータを返してくれるオブジェクトとしてコントローラーで使いまわして、予約Entityを生成し、予約処理を実行することを想定していました。

しかし予約はWebブラウザだけでなくAPIなど複数のフローが存在します。そのフローごとに1度にリクエストされるパラメータが異なるため、予約Entityのプロパティがどこまでセットされるかはそのフロー(リクエスト)に依存します。そこに対応するためにEntity生成までをRequestオブジェクトの責務としました。

# ReservationRequest.php

    /**
     * 予約データを生成する
     */
    public function toEntity()
    {
        $model = ClassRegistry::init('Reservation');
        $plan = $this->plan();
        $schedule = $this->schedule();
        $properties = ['Reservation' => [
            "plan_id" => $plan->id,
            "checkin" => $schedule->checkin(),
            "checkout" => $schedule->checkout(),
            // more properties...
        ]];
        return $model->entity($properties);
    }

RequestオブジェクトからEntityを生成してもらったコントローラーは、予約処理フローを流すことだけに集中してもらうことで、可読性を高くすることができました。

まとめ

ビジネスロジックのゴタゴタをコントローラーに持っていく前に、Requestレイヤーに吸収してもらうと幸せになれます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした