validatable-recordでVue.jsのフォームバリデーションをカイゼンしよう

# まえおき

SPAを作っていると、入力フォームを持つコンポーネントはわりかし頻繁に作る機会がありますよね :joy:

フロントエンドのバリデーションというのは、大抵の場合それっぽいnpmを使ったりして済ませてしまいがちな部分がありますが、実際にはアプリケーション・アーキテクチャにおいてバリデーションというのは必ずしも軽視できないものであることが多いです。特に大規模なアプリケーションのフォームであれば、バリデーションのロジックがビューに絡みついてビューレイヤが肥大化していくことがよくあります。

ということで、今回はVue.jsのコンポーネント実装を例にとって、どのようにしてバリデーション・ロジックをコンポーネントに粗結合な形で実装すればよいかを、[validatable-record](https://github.com/IzumiSy/validatable-record)というOSSの利用例とともに考えてみます[^1]。

[^1]: 元ネタはこのスライドです https://speakerdeck.com/izumisy/hurontoendobaridesiyon

# 実装例
実際に動くコードはこちらのURLから
https://www.webpackbin.com/bins/-L-kLd1mknBzNr67zNxW

## HTML
HTMLは、このような構造のフォームを作ります。

![Selection_043.png](https://qiita-image-store.s3.amazonaws.com/0/18973/7f8abc32-2701-665b-3d1c-5bb2fe7a0736.png)

```html:index.html
<!doctype html>
<html>
　 <head>
    <meta charset="utf-8"/>
    <style>
      [v-cloak] { display: none; }
      .field { margin: 10px 10px; }
      .error { color: red; }
    </style>
  </head>
  <body>
    <div id="app" v-cloak>
      <form @submit.prevent="submit">
        <div class="field error">
          <p v-if="errors">{{ errors[0] }}</p>
        </div>
        <div class="field">
          <input type="text" v-model="form.name" placeholder="Your name" />
        </div>
        <div class="field">
          <input type="number" v-model="form.age" placeholder="Your age" />
        </div>
        <div class="field">
          <button type="submit">Submit</button>
         </div>
      </form>
    </div>
    <script src="main.js"></script>
  </body>
</html>
```
フォームは`@submit.prevent`とすることで、エンターキーでの送信処理をブロックし、Vueコンポーネントへ移譲します。
## JavaScript
JavaScript側ではvalidatable-recordを使ったVueコンポーネントの実装を行っていきます。

### validatable-recordとは
今回使う[validatable-record](https://github.com/IzumiSy/validatable-record)は簡単に言うと、[immutable.jsのRecord](https://facebook.github.io/immutable-js/docs/#/Record)に[validate.js](https://validatejs.org/)を足したライブラリで、イミュータブルオブジェクトへの属性の定義とバリデーションルールの設定を行えるものです。

```js:利用例
// ベースとなる属性の構造とバリデーションルールを定義
const ManRecord = ValidatableRecord({
  name: null,
  age: null
}, {
  name: {
    presence: true
  },
  age: {
    presence: {
      message: "is invalid"
    }
  }
});

// 上で定義したRecordを継承するクラスを定義
class Man extends ManRecord {
  ...
}

const man = new Man({
  name: "Justine";
  age: 25
});

man.validate() // == true

const agelessMan = new Man({
  name: "Michael"
});

agelessMan.validate() // == false

agelessMan.getErrors() // == [ "Age is invalid" ]
```

### 実際のVueコンポーネントでの使用例

まずは、`ValidatableRecord`の定義を継承したクラスを作ります。

ここで`name`や`age`などのフォームの属性の定義や、そのバリデーション・ルール、そしてそれらのデータを使った振る舞い（`submit`などのようなインスタンスメソッド）を定義します。

```js:フォームクラス
class Login extends ValidatableRecord({
  name: null,
  age: null
}, {
  name: { 
    presence: true,
    length: {
      maximum: 20
    }
  },
  age: { 
    presence: true,
    numericality: {
      greaterThanOrEqualTo: 0
    }
  }
}) {
  submit() {
    //
    // Ex: インスタンスのデータを使ってWebAPIをコールしたりする。
    //
  }
}
```
コンポーネント自体にはバリデーションのルールなどを具体的に定義せず、単に`Login`クラスを使った実装のみにすることで、バリデーションのロジックをコンポーネント（ビューレイヤ）から粗結合にすることができます。

```js:フォームコンポーネント
new Vue({
  el: '#app',
  
  data: {
    errors: null,
    name: null,
    age: null
  },
  
  methods: {
    submit() {
      // フォームのデータをもとにフォームモデルのインスタンスを作る
      const login = new Login({
        name: this.name,
        age: this.age
      });
      
      // バリデーションを行い、エラーがあればerrorsにセットし、submitせずreturnする
      if (!login.validate()) {
         this.errors = login.getErrors();
         return
      }
      
      // エラーがなければsubmitを実行する
      login.submit();
    }
  }
})
```
ちなみに、動かしてみるとこのようなかたちになります。

![Peek 2017-12-11 15-20.gif](https://qiita-image-store.s3.amazonaws.com/0/18973/f9a5d6b3-2014-83c3-bcd0-83b42d3e5a45.gif)

ご覧のように、**Age**と**Name**をそれぞれバリデーションし、どちらかが空白の場合にはフォームの上に、対応するエラーの文言が出ていることがわかりますね。

また、この実装の最も重要な点は**ビューレイヤ（VueコンポーネントやHTML）の中にバリデーションロジックの記述がない**というところです。コンポーネントの実装はバリデーションロジックの実装によってFatにならず、またテストの際もバリデーション・ロジックの変更とビューの変更は別のものになり、`Login`クラスが単体でテストをすれば良いことになります。

# この実装のなにがいいの？
> 「なんか面倒くさそうだし、わざわざ`validatable-record`なんて使わなくても、コンポーネントの中で[`filter`を使って入力のタイミングでバリデーション](https://qiita.com/growsic/items/fb57552053cf90f0fc31)したり、[`computed`を使った実装](https://qiita.com/macoshita/items/1e2fd49735b9d4c7f5c7)のほうがいいんじゃない？」

ふむ、たしかにこんな声も聞こえてきそうですね :thinking_face:

## ビューとバリデーションの関係を考える
今回の実装の最も重要な目的は、validatable-recordを使うことではなく、ビューレイヤから、**アプリケーションに固有のルール（ドメイン・ロジック）としてのバリデーション**を切り離すことで、変化に強いアプリケーションへとカイゼンする、というものです。

例えば、今回触れたVue.jsは、フレームワークの性質上Vueコンポーネントがデフォルトで`filter`や`computed`などの様々なビューに関連する機能を提供しており、便利な反面、スケーラビリティを意識せず実装を進めてしまうと、どうしてもビューレイヤとしてのVueコンポーネントが膨らんでしまいがち[^2]になります。

とりわけ、SPAなどのフロントエンド・アプリケーションは、ビューレイヤというデザイン要件の変更などの影響を強く受けやすい部分と近いため、極端に言うとビューはデータを表示するという責務のみを持ち、できるだけ複雑な`if`分岐がビューレイヤには現れないように意識をすることが大事です :muscle:

## ドメインとバリデーション
```
Ex) 「選択できる商品は３つまで」
Ex) 「総額3000円になるように選択必須」
```

たとえば、ECサイトを作っていて現れる上のような条件は、確実にドメイン・ロジックの一部です。なぜなら、これらは**そのアプリケーションに固有の条件**だからです。そのバリデーションが、あなたのアプリケーションによって行われるビジネスによって生まれるものかどうかを考えると、自ずとそのバリデーションがドメインに属するものかどうか？ という点が見えてきます :eye: 

バリデーションがドメイン・ロジックの一部に属するものであるということが分かれば、そのバリデーション・ルールがHTMLやVueコンポーネントの中に記述されていてはいるべきではない、ということも分かります。なぜなら、ビューレイヤの変更によって意図しない影響を受ける可能性が生まれるからです。アプリケーションの中核としての「ドメイン」を関係のないレイヤから切り離して守ることで、保守・運用のしやすい変化に強いアプリケーションにすることができます :ok_hand:

[^2]: このような観点では、Reactのようなビューのコンポーネント化のみに特化している「薄い」ビューレイヤのフレームワークには利点があります。

## 実装と設計とのトレードオフ
とはいえ、このような実装をするかしないか、という点は完全にアプリケーションを開発するチームや組織に依存します。フレームワークによっても、その実装パターンは多種多様で、必ずしもバリデーションがビューにおかれるべき責務ではないと必ずしも言いきれはしません。それでも、今回の[validatable-record](https://github.com/IzumiSy/validatable-record)を用いたVueコンポーネントのカイゼンパターンが何らかの手助けになれば幸いです。