RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

10年物の長寿プロダクトのバリデーションアーキテクチャを変更した件

こんにちは、楽楽販売開発課の岡本です。
弊社では10年を超える長寿プロダクトをいくつも擁していますが、私が担当しているプロダクトもそんな長寿プロダクトの一つです。

さて、どのように優れたプロダクトでも10年以上開発を続けていれば、少なくない量のコード負債を抱えてしまうもので、我々の開発チームでもこの問題に日々悩まされております。
このような状況を打開すべく、昨年9月に開発チーム内に改善専門部隊が立ち上がりました。

本記事では、改善部隊が行った施策の1つである「バリデーションアーキテクチャの変更」を取り上げて紹介しようと思います。

改善施策を決めるまで

先の項でも紹介した通り、我々のプロダクトは少なくない量のコード負債を抱えてしまっています。
ですので、改善部隊が発足時点で改善したい内容はいくらでもあり、正直何を選べば良いのかはっきりとしない状態でした。

このような時に ”感と経験を頼りにピンポイントに改善箇所を見つける” ようなことができれば、手っ取り早いのですが、あいにく私にはそのような技量はないので、まずは開発チームのメンバーが実際にどう感じているのかヒアリングしてみることにしました。

ヒアリングの際に注意したのは 「課題を明確にする」 という点です。
課題が明確でないままヒアリングを行うと方向性が揃わず着地できないことが多いからです。
今回は、「開発速度を上げるには」という課題を設定してメンバーにヒアリングを行い改善の解像度を徐々に上げていきました。

こうして、決定したいくつかの施策のうちの1つが「バリデーションアーキテクチャの変更」です。
では、事項以降「バリデーションアーキテクチャの変更」の実施概要にてついて紹介していきます。

アーキテクチャ概要

まずは、旧アーキテクチャの概要を紹介しておきます。

基本的な処理の流れは、以下の図の通りです。
(一部機微な情報が含まれるのでクラス名等は実際のコードから変更を加えています。)

アーキテクチャの処理フロー

図だけでは、イメージを掴みづらいと思いますので、コード例も載せておきます。

<?php

class UserController {

  /** HTTPリクエストのエンドポイントメソッド */
  public function register(): void {
    // 1.リクエストパラメータを取得
    /**
     * @var array<string, string> $requestParamMap
     * Note: [name => 'ユーザ氏名', age => 'ユーザ年齢']
     */
    $requestParamMap = $this->getRequest()->getParams();

    // 2.リクエストパラメータから バリデーション実行オブジェクト を生成
    $builder = new UserValidationBuilder($requestParamMap);
    $validationProcessor = $builder->getRegisterValidation();

    // 3.バリデーションを実行して結果出力
    if (!$validationProcessor->isValid()) {
      echo $validationProcessor->getMessage();
      return;
    }
  }
}

class UserValidationBuilder {
  /**
   * @param array<string, string> $requestParamMap
   * Note: [name => 'ユーザ氏名', age => 'ユーザ年齢']
   */
  public function __constoracluct(
    array $requestParamMap
  ) {
  }

  public function getRegisterValidation(): ValidationProcessor {
    // 1. リクエストパラメータとバリデーションのマッピングを指定
    $validationMapping = [
      'name' // $requestParamMap['name'] の値を getNameValidate で指定された内容で検証
        => $this->getNameValidation('ユーザ氏名'),
      'age'
        => $this->getAgeValidation('ユーザ年齢')
    ];
    // バリデーション実行オブジェクトを返す
    return new ValidationProcessor($validationMapping, $this->requestParamMap);
  }

  private function getNameValidation(string $itemName): array {
    // 2. ユーザ氏名のバリデーションを設定
    return [
      new NotEmptyValidation($itemName), // 必須
    ];
  }

  private function getAgeValidation(string $itemName): array {
    // 3. ユーザ年齢のバリデーションを設定
    return [
      new NotEmptyValidation($itemName), // 必須
      new IsNumberValidation($itemName), // 数値入力のみ
    ];
  }
}

アーキテクチャの問題分析

さて、旧アーキテクチャですがコード例を見てどのように思われたでしょうか?
私は、これを見た瞬間「読みにくいな~」という印象を持ったことを覚えています。

では、なぜ「読みにくい」と感じたのか、これがわからないままでは改善の方針も立てられないので、まず最初にこれの言語化を実施しました。

■実際に言語化した内容

  • リクエストパラメータが配列で扱われているので、パラメータ追加等の改修で発生する影響度が調査しづらい
  • 対象項目の指定と、その項目のバリデーション内容の指定が、別々の関数で行われており認知負荷が高い
    (画面をスクロールしないと項目に対するバリデーション内容がわからない)

どうでしょうか?
色々と意見もあるかと思いますが、私はこのように言語化し、そこから以下ような大方針を立てアーキテクチャの設計を行うことにしました。

  • リクエストパラメータを配列で扱うことをやめる
  • どの項目に、何のバリデーションが実施されるのかを、画面スクロールなしに把握できる状態にする
    • Java で言うところの BeanValidation のようなものを想定

そして、本方針のもとPOCを作成し、更に検討をかさねたすえ、無事新アーキテクチャの設計はFIX、昨年11月頃より本格的に導入を開始することができました。

アーキテクチャ概要

では、最終的な新アーキテクチャの概要も以下に紹介しておきます。 (旧と同じく処理フロー図とコード例)

アーキテクチャの処理フロー

<?php

class UserController {

  /** HTTPリクエストのエンドポイントメソッド */
  public function register(): void {
    // 1.リクエストパラメータをFormに変換
    /**
     * @var array<string, string> $requestParamMap
     * Note: [name => 'ユーザ氏名', age => 'ユーザ年齢']
     */
    $requestParamMap = $this->getRequest()->getParams();
    $form = new UserRegisterForm(
      $requestParamMap['name'],
      $requestParamMap['age']
    );

    // 2.From の内容を検証(バリデーション実行オブジェクトではなく、実行結果が返ってくる)
    $result = ValidationBuilder::validate($form);

    // 3.結果出力
    if (!$result->isValid) {
      echo $result->message;
      return;
    }
  }
}

class UserRegisterForm {
  public function __construct(
    // property と attribute でバリデーションマッピングを再現
    // ValidationBuilder がこの定義を取り出して、これまで各自で実装していた ValidationProcessor を自動生成する
    #[NotEmpty('ユーザ氏名')]
    public ?string $name,
    #[NotEmpty('ユーザ年齢')]
    #[Number('ユーザ年齢')]
    public ?int $age
  ) {
  }
}

最後に

こうして生み出された新アーキテクチャですが、半年ほど置き換え作業を継続し、5月末段階時点で2割程度の完了率となっています。
なにせ、10年越しの負債を解消していっているので、そう易々とは進めさせてもらえないのが現状です。
とはいえ、チーム内から良いフィードバックを多々もらっており、少しづつでも負債解消が進んでいることをメンバーも実感できているようです。
以上、我々のチームの改善取り組みの紹介でした。

Copyright © RAKUS Co., Ltd. All rights reserved.