🚥

「バリデーション」と「Specificationパターン」は何が違うのか

に公開2

はじめに

バックエンドエンジニアの角方です!

先日、あるPRレビューのコメントで「Specificationパターン」という言葉を初めて知りました。
それまでは業務ルールのチェックも、入力値のチェックも、if文で書いていたのですが、
「バリデーションと何が違うんだろう?」と思いながら調べてみると、たしかに自分が書いていたコードの中には、業務ルールをif文にベタ書きしていたものがあり、もっと別の場所に置くことを考慮すべきだったなと思いました🤔

調べているうちに、「Specificationパターン」と「バリデーション」はどちらも条件を判定するという見た目は同じなのに、目的や置き場所が全く違うことがわかってきました。同じように混同していた自分と同じような人のために、この記事ではJavaScriptの例を使いながら、自分なりにまとめます!

バリデーションとは何か

バリデーションは、データの形が正しいかをチェックする処理です。主に入力値(フォーム入力やAPIのリクエストボディなど)に対して行われます。

function validateSignupInput(input) {
  const errors = [];

  if (!input.email.includes("@")) {
    errors.push("メールアドレスの形式が不正です");
  }
  if (input.age < 0 || input.age > 150) {
    errors.push("年齢の値が不正です");
  }

  return errors;
}

ポイントは、ここで見ているのが「そもそも値として成立しているか」ということだけだという点です。メールアドレスの形式、必須項目の有無、文字数の上限など、システムに入ってくるデータの品質を守るための門番がバリデーションです。

Specificationパターンとは何か

Specificationパターンは、ビジネスルール(業務上の条件)をオブジェクトとして表現するパターンです。判定対象は「入力値」ではなく、すでに存在しているドメインオブジェクトです。

例えば「プレミアム会員特典を受けられる会員」という業務ルールで考えてみます。

  • プレミアム会員であること
  • 登録から1年以上経過していること
  • 累計購入額が10万円以上であること
class Member {
  constructor(registeredAt, totalPurchaseAmount, isPremium) {
    this.registeredAt = registeredAt;
    this.totalPurchaseAmount = totalPurchaseAmount;
    this.isPremium = isPremium;
  }
}

class EligibleForPremiumDiscount {
  isSatisfiedBy(member) {
    const oneYearAgo = new Date();
    oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

    return (
      member.isPremium &&
      member.registeredAt <= oneYearAgo &&
      member.totalPurchaseAmount >= 100000
    );
  }
}

単なる値の形式チェックではなく、「会員」という概念に対して業務上意味のある判定を行っています。

条件が増えても、既存のSpecificationを組み合わせるだけで新しいルールを作れます。

何が違うのか

観点 バリデーション Specificationパターン
目的 データの形式・整合性のチェック 業務ルールの判定
判定対象 入力値(文字列、数値など) ドメインオブジェクト(会員、注文など)
実行タイミング 入力を受け取った直後 業務ロジックの中
「メール形式が正しいか」「必須項目が空でないか」 「送料無料の対象か」「割引を受けられる会員か」
失敗した場合 「入力をやり直してもらう」 「その条件に該当しない、という業務上の結果」

一番わかりやすい見分け方は、「それが崩れたら『入力ミス』なのか、それとも『そのルールには当てはまらないだけ』なのか」を考えることです。

  • メールアドレスに@がない → 明らかな入力ミス → バリデーション
  • 会員登録してからまだ半年しか経っていないので割引対象外 → 入力ミスではなく、業務上そういうルールなだけ → Specification

具体例で流れを追ってみる

会員登録フォームからの入力を例に、両者がどこで働くか見てみましょう。

// 1. バリデーション: 入力値の形式チェック
const errors = validateSignupInput(rawInput);
if (errors.length > 0) {
  throw new Error(errors.join(", "));
}

// 2. ドメインオブジェクトを組み立てる
const member = new Member(
  new Date(rawInput.registeredAt),
  rawInput.totalPurchaseAmount,
  rawInput.isPremium
);

// 3. Specification: 業務ルールの判定
const discountSpec = new EligibleForPremiumDiscount();
if (discountSpec.isSatisfiedBy(member)) {
  applyPremiumDiscount(member);
}

バリデーションは入り口で1回だけ行われる関門です。一方でSpecificationは、会員が存在する限り、アプリケーションのあちこちで(注文時、キャンペーン適用時、管理画面での絞り込み時など)繰り返し使い回されるルールです。

まとめ

  • バリデーションは「入力データの形式が正しいか」を守るための処理
  • Specificationパターンは「ドメインオブジェクトが業務ルールを満たすか」を表現するパターン
  • 見分け方は、「入力ミスかどうか」「入り口だけの話か、業務ロジックの中で何度も使うルールか」を考えること

どちらも「条件判定」という見た目は似ていますが、役割は明確に異なります!
この違いを意識すると、「このチェックはどこに書くべきか」という設計判断がしやすくなると思いました!!!

株式会社ソニックムーブ

Discussion

takemo101takemo101

「境界での入力チェック」と「ドメインの業務ルール」をしっかり分ける視点、とても共感しました。

一点だけDDD(原書)の補足なのですが、Specificationパターンの真価って実はメモリ上のValidation以上に「リポジトリの検索条件(Selection/Query)として渡してDBクエリに変換し、ドメインのルールを保ったまま効率よくデータを抽出する」 という用途に大きかったりします。

なので対立構造というよりは、「入力値バリデーション」と「ドメインルール(Specificationを使って検証や検索に使い回すもの)」という整理にすると、DDDの文脈やリポジトリとの連携にもスッと繋がりそうだなと思いました!

「入力バリデーション」と「ドメインルール(検索や検証をカプセル化したSpecification)」という位置づけで捉えると、DDD本来の定義やデータ抽出の実務にも綺麗にハマるかなと思いました!

良いテーマの共有をありがとうございました!

1
SMV_角方SMV_角方

「入力バリデーション」と「ドメインルール(検索や検証をカプセル化したSpecification)」という位置づけで捉えると、DDD本来の定義やデータ抽出の実務にも綺麗にハマるかなと思いました!

おっしゃる通り、「バリデーション vs Specification」という対立構造というよりかは、Specificationの本領は「ドメインルールをカプセル化して、検証にも検索条件にも使い回せる」という点にある形のほうが理解しやすいですね!
コメントありがとうございます、とても勉強になりました!!!

1