LoginSignup
90
57

More than 1 year has passed since last update.

コマンド・クエリという考え方

Last updated at Posted at 2019-12-11

オープンロジアドベントカレンダー2019の11日目の記事です。

12/7にLaravelで複雑性と戦うための4つのTipsという記事に続き、2つめの記事になります!

この記事は、個人的にコマンド・クエリを探求したものになります。
前半は割とまじめですが、後半はほぼネタになっているので、気楽に読んでください。
コマンド・クエリがもっと身近に感じることで、コマンドクエリ分離原則やコマンドクエリ責務分離に興味が持てれば幸いです。

まずはじめに、コマンドクエリ分離原則を簡単に説明したいと思います。

コマンドクエリ分離原則(CQS)

Bertrand Meyer氏が考えたコマンドクエリ分離原則(CQS)です。
青い鈍器と呼ばれることもある「オブジェクト指向入門」に記載されています。
私は、鈍器ということで怖くてまだ読めていません・・・。

ただ、考え方自体はとてもシンプルで、下記がすべてです。

基本的な考えは、オブジェクトのメソッドを明確に2つのカテゴリに分類するというものである。

・問い合わせ:結果を返し、システムの状態を変更しない(副作用がない)
・コマンド:システムの状態を変更し、値を返さない

引用元: Martin Fowler's Bliki (ja): コマンド・問い合わせの分離

言葉だけだとわかりづらいので、簡単な加算減算できるクラスを実装してみましょう。
まずは要件を決めます。

  • 加算ができる(コマンド)
  • 減算ができる(コマンド)
  • 結果が取得できる(クエリ)

上記をコマンドクエリ分離原則(CQS)を基にJavaScriptで実装してみます。


/**
 * 計算クラス
 */
class Calculator {
    constructor(value) {
        this.value = value;
    }

    /**
     * 加算
     * @param num
     * @returns Calculator
     */
    add(num) {
        // コマンドなので、メンバ変数を更新する副作用がある
        this.value = num + this.value;
        // 副作用があるので、自身のインスタンスを返す
        return this;
    }

    /**
     * 減算
     * @param num
     * @returns Calculator
     */
    sub(num) {
        // コマンドなので、メンバ変数を更新する副作用がある
        this.value = this.value - num;
        // 副作用があるので、自身のインスタンスを返す
        return this;
    }

    /**
     * 結果
     * @returns 結果
     */
    getResult() {
        // クエリなので、副作用がない。そのため、データの取得だけにしか使えない
        return this.value;
    }
}

実際の使い方は下記になります。

// インスタンス作成
const calculator = new Calculator(1);
const result = calculator
    .add(2) // コマンド(副作用あり)
    .sub(1) // コマンド(副作用あり)
    .getResult() // クエリ(副作用なし)
// expected output:  1

とても簡単な概念だと思います。たったこれだけですが、下記のようなメリットがあります。

  • メソッド名で何をするのかわかる
  • コマンドとして定義したメソッドでは、副作用があることが期待できる
  • クエリとして定義したメソッドでは、副作用が発生しないことが期待できる
  • コマンドの順番を入れ替えたとしても成立する
    • コマンドとして定義したメソッドでは、自身のインスタンスを返すことにより、チェーンメソッドとして定義できる。

コマンドとクエリを考えるだけで、とても柔軟な実装にすることができるのがとても素晴らしいですね!

コマンドクエリ責務分離(CQRS)

Greg Young氏がコマンドクエリ分離原則(CQS)をオブジェクト指向としてだけではなく、アーキテクチャとして定義したものです。
CQSをベースに考えて入るので、基本概念はシンプルですが、アーキテクチャに適用したことにより、考える要素が増えているため、複雑度があがっています。
ここでは、深く深堀しませんが、気になる方はぜひ下記を読んでみてください!

コマンドとクエリを意識するだけで、アーキテクチャまで考えることができるなんて、とても素晴らしいですね!

そして、少し余談になりますが、私は下記のCQRSの記事の一文を読んだことがきっかけで、コマンドとクエリを意識するようになりました。

CQRSに対する批判的見解

読み込み中心の環境でスケーラビリティを達成する,もうひとつの手段が,マスタ/スレーブレプリケーションだ。データを更新するひとつのマスタと,すべての変更を複製するひとつ以上のスレーブで構成する。古くから存在し,どのデータベースでもサポートされている機構だが,書き込みと読み込みを分離しているという意味から,これもCQRSの一種であるとDahan氏は定義する。

マスタ/スレーブレプリケーションをCQRSと考えることができ、自分の知らないところでCQRSに触れている可能性があるということにとても感動しました。
というわけで、ここからは、コマンドとクエリがもっと身近にあり、知らぬ間に使っていることがわかるような形で考えていければと思います!
※CQS、CQRSという表現だと語弊がある場合があるので、ここから先は、コマンド・クエリと表現することが多くなります。

jQuery

最近、不遇の扱いを受けているjQueryですが、コマンドクエリの分離原則に則ったとても良いライブラリです。
メソッドチェーンとして表現できているのもとても良いですね!


// 初期化
$('<a>') 
    .attr('id', 'link')    // idを付与するという副作用があるのでコマンド
    .text('Hello world!')  // Hello world!というテキストを付与するという副作用あるのでコマンド
    .get(0) // elementとして作成する処理はあるが副作用が起きていないのでクエリ

attr()text()などは、状態を更新しているため、結果を取得することができません。
get()に関しては、結果を取得することができますが、副作用のある処理がないので、get()を何度実行しても、同じ結果を返すことができます。

SQL

SQLはコマンドとクエリで分離できている良い例です。

SQLのDML(Data Manipulation Language)をコマンドとクエリで分けてみます。
※SQLにもクエリという言葉があるので、紛らわしいですね・・・。

DML コマンド or クエリ 補足
SELECT クエリ(副作用なし) 取得
INSERT コマンド(副作用あり) 新規作成
UPDATE コマンド(副作用あり) 更新
DELETE コマンド(副作用あり) 削除

上記のように分けることができます。
INSERT、UPDATE、DELETEに関してはDBのデータに対して更新だけの責務を持っています。
SELECTをした時にはDBに対して更新が行われないので、何度SELECTを実行してもDBに格納されたデータが更新されることはありません。
そのため、DBのデータを更新したら、確認するためにはSELECTをするという一方通行のフローが生まれています。
SQLの場合は、コマンドとクエリを完全に分離しているため、INSERT後にIDを知りたいためには、SELECTを行うという徹底ぶりを感じることができます。

Post Redirect Get パターン(PRGパターン)

フォームデータの二重送信を防止する手法ですが、これもコマンドとクエリに分離された良い例です。
簡単に言うと、POSTなどをした際にリロードをすると再度POSTされるという問題を、POSTしたらGETへリダイレクトすることにより解決しています。

PostRedirectGet_DoubleSubmitSolution.png

引用元: [Wikipedia: Post/Redirect/Get] (https://en.wikipedia.org/wiki/Post/Redirect/Get)

ここでは詳しくPRGパターンについては言及しませんので、詳しく知りたい方は下記の記事をご参照ください。

PRGパターンを基に、よく使用するHTTPリクエストメソッドをコマンドとクエリで分けてみます。

メソッド コマンド or クエリ 補足
GET クエリ(副作用なし) リソース取得なので、クエリ
POST コマンド(副作用あり) リソース新規作成なので、リダイレクト
PUT コマンド(副作用あり) リソース更新なので、リダイレクト
DELETE コマンド(副作用あり) リソース削除なので、リダイレクト
PATCH コマンド(副作用あり) リソース更新なので、リダイレクト

フォームデータの二重送信を防止する手法から生まれてはいますが、綺麗にコマンドとクエリで分離できていますね!

Laravel、RailsなどのフルスタックなWebフレームワークでは、上記のように副作用のある処理をしたら、リダイレクトしてレンダリングされていると思います。
個人的には、副作用が発生したら、リダイレクトされてレンダリングされるという処理が、Reactに通じるところがあり、とても興味深いと感じています。

ただ、APIだと無駄なネットワークを消費しないために、PRGパターンでは実装しないのが一般的なため、気をつける必要があります。
コマンドとクエリを分離すれば、フローとしては綺麗になりますが、それが全体の最適化を考えた場合に必ずしも正しくないという例になります。

Updates & creation should return a resource representation

引用元: Vinay Sahni: Best Practices for Designing a Pragmatic RESTful API

React

PRGパターンで、一方通行のフローで例えることができたので、ゴリ押し感がありますが、Reactでも通じるところがあるので考えてみましょう。
Reactでは、event実行時にStateの更新が行われたら、その更新を検知し、DOMが再レンダリングされるという特徴を持っています。

かなり単純ですが、下記のイメージになります。

Screenshot from 2019-12-11 15-12-43.png

ざっくりですが表にすると下記になります。

処理 コマンド or クエリ 補足
render() クエリ(副作用なし) DOMのレンダリング
setState() コマンド(副作用あり) Stateというリソースの更新

Reactでもこのようにして考えるとコマンドとクエリで表現することができると思います。
また、Fluxでは、CQRSと類似と言われることも多くあり、その点でもコマンド・クエリを身近に感じることができますね。

ファイル

今まで、少し難しい話が多かったので、よりライトにしていきたいと思います。
みなさんは、コンフィグファイルやテキストファイル、エクセルファイルなどを編集したことはありますか?
その時にファイルを変更後、再度ファイルを表示して正しく保存されているかを確認された方もいるのではないでしょうか。
自分は心配性なので、これをよくしています。

ある時、これも立派なコマンドとクエリであることに気づきました。

動作 | コマンド or クエリ
--- | --- | ---
ファイルを変更 | コマンド(副作用あり)
ファイルを開く | クエリ(副作用なし)

ファイルを変更した状態で見ているものは、その状態が正しく保存された状態か判断することができません。
ファイルを再度開くことにより、副作用のない状態で、データの取得が行なえます。
コマンドとクエリを意識せずにやっていると言えるでしょう!

普段ついついしてしまう行動でも実はコマンドとクエリに分離して考えることができましたね!

呼吸

ファイルを開くよりももっと身近にコマンドとクエリを感じることもできます。
それが呼吸です!

ざっくりですが、コマンドとクエリにまとめてみましょう。

動作 | コマンド or クエリ
--- | --- | ---
息を吸う | コマンド(副作用あり) | 外界から酸素(O2)を取り入れ、体内で消費
息を吐く | クエリ(副作用なし) | 二酸化炭素 (CO2) を放出

参考: Wikipedia: 呼吸

私達は、息を吸うことにより酸素を吸収するというコマンド(副作用あり)を実行し、息を吐くことにより、二酸化炭素を出すというクエリ(副作用なし)を実行しています。
※呼吸で副作用と表現していますが、あくまで例えです。

上記の通り、私達は、日々生きているだけで、コマンドとクエリを繰り返していますね!

まとめ

後半は完全にネタですが、コマンドとクエリが実は身近なところにあることがわかったのではないでしょうか!
みなさんも身近にあるコマンド・クエリを探してみてください!

90
57
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
90
57