CRUDからCQRSへ還る回帰の旅
BEAR.Sunday Advent Calendar 2025最終日、最後の記事は
Data Mapperをめぐる二つの世界観、CQRSをめぐる二つの世界観 の続きです。
データマッパーによる「貧血モデル」の肯定、そしてCQRSによる「意図(Command)」の発見。長い議論の末、koriymとたつきちは、現代のWeb開発における「違和感」の正体——CRUDという思考の檻——に辿り着きました。
物語は、その「檻」の外に出るための、最後の対話へと向かいます。
⚠️ 重要: この議論の二人は実在の人物と一切関係がありません。ご注意ください。
ラウンド10:UpdateEntityの憂鬱
駅へと続く夜道。冷え込みが強くなってきたが、二人の議論の熱はまだ引いていなかった。
前回の議論で、CQRSの「C(Command)」が単なる書き込みではなく、「業務の意図」であることは理解できた。しかし、たつきちの頭の中には、長年染み付いた「実装の手癖」が消えずに残っていた。
たつきち:
「koriymさん、概念としての『意図』は分かりました。でも……現場に戻ってキーボードを叩く時、僕らが書くのは結局 ORMの保存のコードですよね?『退会する』という意図があったとしても、それを実装しようとすれば、$user->setIsActive(false) を呼んで、$em->flush() するだけです。どれだけ高尚な名前をつけても、最終的にやっていることはエンティティのプロパティ書き換え、つまり『データの更新(CRUD)』じゃないですか。意図と実装のこのギャップを、どう埋めればいいんですか?」
koriymは立ち止まり、ポケットからスマートフォンを取り出した。
koriym:
「そのギャップに苦しむのは、たつきちさんが『データベースの都合』を『ユーザーの関心』だと思い込んでいるからです。これを見てください。」
koriymが画面に表示したのは、CQRSの提唱者、Greg YoungによるGitHub上でのコメントだった。
たつきち:
「……上は汎用的なデータ更新、下は専用のコマンド、ですね。さっきの話通りです。でも、結果としてDBの値が false になるなら同じことでは?」
koriym:
「結果が同じなら、プロセスも同じでいい。 ……本当にそうでしょうか?
たつきちさん、ユーザーの関心はどこにあると思いますか? 『自分のレコードの active カラムを false に書き換えること』でしょうか?」
たつきち:
「いや、ユーザーはDBのカラムなんて知りません。『退会したい』、ただそれだけです。」
koriym:
「そう。『退会したい』という目的(Command)が達成されるなら、裏側でレコードが更新されようが、削除されようが、あるいは紙の台帳に赤線が引かれようが、ユーザーには関係がない。つまり、『DBでINSERTになるかUPDATEになるか』は、ユーザーの関心事ではないんです。」
たつきち:
「ユーザーの関心ではない……。」
koriym:
「なのに私たちは、上の行(UpdateEntity)のように書くことを『システム開発』だと呼んできた。
ユーザーの『やりたいこと』よりも、データベースの『都合(行の更新)』を優先してAPIやクラスを設計してしまった。それが、私たちが陥っている『CRUD思考』の正体です。」
ディスカッションの分析
ここでの議論は、エンジニアが陥りやすい「手段の目的化」を浮き彫りにしています。
-
実装(How)への固執:
たつきちは「結局はSQLのUPDATE文になる」という実装結果(How)にとらわれ、その前段にある「なぜそれを行うのか(Why/What)」という意図の表現を軽視しています。 -
関心の分離の再定義:
koriymは、「DBのカラム操作」は実装詳細(Implementation Detail)であり、ユーザーの関心(User Concern)ではないと断言しました。
「UpdateEntity」はDB都合の言葉であり、「DeactivateCustomer」はドメイン(業務)の言葉です。この二つを混同しないことが、設計の第一歩となります。
ラウンド11:熱狂の残骸と「書きやすさ」の罠
たつきち:
「でも……それが一番『書きやすい』じゃないですか。フレームワークを使えばScaffoldすれば一瞬でCRUDが作れる。その生産性は正義だと思ってました。」
koriym:
「生産性、ですか。そこに『書きやすさ(Writability)』への偏重がないですか?」
たつきち:
「Writability……? 書きやすさと生産性って、同じことじゃないんですか?」
たつきちは怪訝な顔をした。コードが早く書けるなら、それは生産性が高いということだ。それ以外の何があるというのか。
koriym:
「違います。そしてその勘違いこそが、かつて私たちを熱狂させ、そして何も残さなかった……。たつきちさんは『CoffeeScript』を覚えていますか?」
たつきち:
「うわ、懐かしい。10年以上前ですか。波括弧も書かなくていいし、Rubyみたいにスラスラ書けるって熱狂しましたね。」
koriym:
「で、あれは何を解決しましたか?」
たつきちは言葉に詰まった。
koriym:
「タイプ数は減りました。見た目も綺麗になりました。でも、非同期処理の難しさも、ドメインの複雑さも、本質的な課題は何一つ解決しませんでした。私たちはいつも『楽に書けること』に熱狂します。ORMもそうです。SQLを書くのが面倒だから、メソッド一つで保存できるようにした。でも、その『楽さ』の代償として、私たちは『SQLが持っていた分別』を失ったんです。」
たつきち:
「SQLの……分別?」
koriym:
「SQLは本来、CQS(コマンド・クエリ分離)を体現していました。SELECT(副作用のないクエリ)と、UPDATE/INSERT(戻り値のないコマンド)。明確に分かれていたんです。
それを『オブジェクトを保存する』という曖昧な処理に丸め込み、CoffeeScriptのように『書きやすさ』を追い求めた結果、私たちはコードから『意図』を奪ってしまった。
ビジネスロジックがコードに語られているか。Readabilityはあるか。本当はそれが大事なのに、『書くのが速い、楽』ことばかりに囚われて。」
ディスカッションの分析
「書きやすさ(Writability)」と「読みやすさ/意図の明確さ(Readability/Intent)」の対立です。
-
Writabilityの罠:
汎用的なCRUDメソッドや、シンタックスシュガーは「書く時間」を短縮します。しかし、ソフトウェアのライフサイクルにおいて、コードが書かれる時間はごくわずかです。 -
失われた規律:
SQLが本来持っていたCQS(読み書きの分離)という優れた規律を、ORMの「便利さ」が覆い隠してしまいました。koriymは、ツールによる抽象化が、かえって本質的な設計能力を退化させた可能性を指摘しています。
ラウンド12:HTMLへの八つ当たり
たつきち:
「意図の話は分かりました。でもkoriymさん、Webの基本であるHTMLフォーム、あれ貧弱すぎませんか? method="GET" と method="POST" しかない。
こっちはコントローラーで update() や delete() を実行したいのに、HTMLがそれをサポートしてくれない。だから _method=PUT なんて隠しフィールドで誤魔化すことになる。これって、Web標準の進化が止まってるせいですよね?」
koriym:
「たつきちさん。それはHTMLが貧弱なのではありません。Webは『データベースのクライアント』になることを拒否しているんです。データベースなら UPDATE や DELETE が必要でしょう。でもWebは違います。あるのは『状態を見る(GET)』か、『意図を送る(POST)』か。その二つだけです。」
たつきち:
「見るか、送るか……。あ!」
たつきちは、ハッとして足を止めた。
たつきち:
「……待ってください。GETは副作用のないクエリ。POSTは意図を伴うコマンド。これって、さっき話していたCQRSそのものじゃないですか。それをデータモデルのように考えて勝手にフラストレーションを感じてただけなんですね」
ディスカッションの分析
ここが物語の大きな転換点(Aha! Moment)です。
-
Webは最初からCQRSだった:
- GET (Query): 副作用なし、安全、キャッシュ可能。
- POST (Command): 副作用あり、意図の送信。
HTMLの仕様は「不便」なのではなく、ある意味「CQRSの強制」でした。
-
設計の放棄:
「HTMLにPUTがない」と文句を言うのは、ドメイン(意図)の設計を放棄し、データベース操作(CRUD)をUIに直結させようとしている証拠です。koriymはこれを「設計の排除」と厳しく指摘しました。
ラウンド13:「正解はない」という逃げ場所
駅へと続く道。たつきちは、業界で最も愛され、同時に最も「諦め」を正当化してきた言葉を口にした。
たつきち:
「……koriymさん。現場は生き物です。リソースは限られているし、ビジネスは常にスピードを求めます。だから僕らは 『設計に正解はない』 と言い聞かせて、その場その場の最適解を泥臭く選んできたんです。現に今のシステムでも利益は出ています。ビジネスが回っている以上、それが正解なんじゃないですか?」
koriymは立ち止まり、静かに首を振った。
koriym:
「たつきちさん。それは因果が逆です。利益が設計の価値を語るのではなく、利益が技術負債を見えなくしているだけです。市場の熱狂が、構造の歪みを覆い隠している。利益が出ているから正解なのではなく、利益が負債を許容しているだけなのです。」
たつきち:
「許容……。」
koriym:
「『品質をとるか、速度をとるか』。この二項対立に陥った時点で、設計は敗北しています。設計とは本来、『速度を維持するために、あえて行わないこと』 つまり制約を決めることなんです。
制約を捨て、何でもありにするのは、柔軟さではありません。単に 大きな泥だんご(Big Ball of Mud)を作っているだけです。泥だんごは、不誠実なエンジニアが作るものではない。むしろ、制約を持たぬまま、ビジネスの要求速度に最も『誠実』に応えようと、最速のパス(CRUD)を走り続けた結果なのです。」
たつきち:
「誠実さの結果、ですか……。僕はてっきり、どんなシステムも年月が経てば、必ず汚れて行き詰まっていくものだと思ってました……。」
ディスカッションの分析
-
因果の逆転:
「利益が出ているから正しい」のではなく、「利益が負債を許容している」だけ。市場の熱狂が冷めた時、構造の歪みは一気に顕在化します。 -
誠実さのパラドックス:
「汚いコード」はエンジニアの怠慢ではなく、しばしば「ビジネスへの過剰な適応」から生まれます。 -
設計の定義:
設計とは「何ができるか」ではなく「何をしてはいけないか(制約)」を定義することです。この制約が欠けた時、システムは因果が予測不能なエントロピーの塊へと「進化」してしまいます。
ラウンド14:20年分の重力と「自己破産」
たつきち:
「……でもkoriymさん。その『重力』に逆らって歩けと言うのは、無理なことだと思うんです。20年かけて指が覚えたCRUDの条件反射を、意識だけで変えるなんて。」
koriym:
「ええ、その通りです。だからこそ、設計が必要なのです。たつきちさん。設計とは『何ができるか』を増やすことではなく、 何をしてはいけないか(制約) を定義することです。この制約を欠いたシステムは、放っておけば必ずエントロピーが増大し、因果関係が予測不能なカオスへと成り果てます。防波堤のない開発は、どこを触ってもどこかが壊れる、確率論的な博劇に過ぎません。」
たつきち:
「でも、最悪の場合は『全面書き直し』をすればいい、という空気もありますよね。それも一つの生存戦略なんじゃないですか?」
koriymは、たつきちの目を真っ直ぐに見つめた。
koriym:
リビルド、リアーキテクチャという響きの良い言葉で、全面書き直しを美化してはいけません。それは敗北であり、本来守るべきだったビジネスの『意図』を喪失し、積み上げた債務を払い切れなくなった末の 技術的自己破産 です。適切な設計という骨格さえ生きていれば、その『意図』は資産として次世代へ継承できたはずなのです。
そして、その破産を招くのはエンジニアの怠慢ではなく、皮肉にも私たちの『誠実さ』です。だからこそ、人の意志や頑張りに規律を委ねることを、一度やめなければならない。」
たつきち:「意志に頼るのを、やめる……?」
koriym: 「納期やビジネスの圧力に晒された時、真っ先に摩耗するのは『気をつける』という個人の意志です。切迫した現場で、規律を維持するコストを個人の精神力に負担させること自体、本来は残酷なことではないでしょうか。
だから、システムの方にその重荷を肩代わりさせるんです。 構造によって 『間違えようのない制約』 を敷き、善意が暴走する余地を物理的に奪うこと。それこそが、20年の慣習という重力から現場を解放し、ビジネスを破局から守る、アーキテクトに許された唯一の優しさだと私は信じています。」
ディスカッションの分析:
「全面書き直し」が必要になったという事態は、単に「コードが古くなった」ことを意味しません。それは「システムの文脈(Context)と意図(Intent)が完全に喪失した」ことの証明です。
-
知的資産の破棄:
本来、コードは「ドメイン知識の結晶」であるはずです。しかし、設計が不在でCRUDの泥だんごになったコードは、もはや「何をしたいか」を語りません。解読不能な暗号と化したとき、人は「捨てて作り直す」という最もコストのかかる選択肢を選ばざるを得なくなります。 -
避けられたはずの失敗:
「技術の流行が変わったから」というのは、多くの場合リビルドの言い訳に過ぎません。真の理由は、「内部構造がビジネスの変化に追従できるだけの『抽象の壁(制約)』を持っていなかった」 ことにあります。 -
「還るべき場所」の不在:
適切な制約(RESTやCQRSなど)の上にあるシステムは、言語や環境が変わっても「意図」が保存されているため、移植や部分的な刷新が可能です。全面書き直しに追い込まれるのは、還るべき「骨格(アーキテクチャ)」を最初から持っていなかったからです。
ラウンド15:錆びない意図
koriym:
「手前味噌になりますが、私が作っている『BEAR.Sunday』というフレームワークがあります。1 これは徹底してRESTの原則——つまりリソース指向アーキテクチャ(ROA)——に従って設計されています。
リソースごとにクラスを分け、GETで状態を返し、POSTで意図を受け取る。そうやって作っていたら、意図せずして、自然と『CQRS』になり、依存関係逆転の原則を守った『クリーンアーキテクチャ』になっていました。」
たつきち:
「へえ、狙ったわけじゃなく、RESTを突き詰めたらそうなったと。」
たつきちはスマホを取り出し、GitHubでそのリポジトリを検索した。
そして、少し気まずそうに言った。
たつきち:
「……でも、失礼ですけど、スター数はそんなに多くないですね。LaravelやSymfonyに比べると、桁が違います。」
koriymは一瞬、目を合わせた後にタブレットを取り出し始めた。
たつきちは困惑した。スター数、ダウンロード数、Qiitaのトレンド入り。それが技術選定の正義であり、安心材料ではなかったのか。
koriym:
「では、これを見ていただけますか?」
koriymはタブレットのある古いシステムのコードを見せた。
たつきち:
「これは……PHP 5ですか? array() 構文だし、修飾子もない。かなり古いコードですね。」
koriym:
「10年以上前に私が書いたシステムです。派手な機能は何もない、クラシックなアプリケーションです。でも、読んでみてください。」
たつきちは画面をスクロールした。そこには、マジックメソッドも、複雑な継承ツリーも、隠蔽されたSQLもなかった。あるのは、明確な名前がついたクラスと、単純なSQL、そして「何をするか」が直球で書かれたメソッドだけだった。
たつきち:
「……読める。すごく読みやすいです。フレームワークの知識がなくても、ここで何が起きて、どういう意図でデータが動くのか、一目で分かります。」
koriym:
「制約が効いているからです。『リソース』という枠組みと、SQLという『データドメイン言語』が明確に分離されている。だから、もしこれを今のPHP 8.5に書き換えようと思ったら、生成AIに投げれば一瞬で終わります。
なんなら、Go言語やRustに移植することだって容易です。なぜなら、ここにあるのは『特定のフレームワークへの依存』ではなく、純粋な『ビジネスの意図』だからです。」
たつきち:
「……!」
ディスカッションの分析
-
Popularity vs Longevity:
スター数やトレンドは「現在の人気」を示しますが、「コードの寿命」は保証しません。フレームワークの便利機能(マジック)に依存すればするほど、技術的負債化のリスクは高まります。 -
Portability of Intent:
「意図(ビジネスロジック)」と「実装(フレームワーク/言語)」が分離されたコードは、驚くほどポータブルです。PHP 5時代のコードであっても、設計が正しければ、最新のAI支援を受けて容易に現代の環境(PHP 8.4やRustなど)へ蘇らせることができます。
ラウンド16:回帰する問い
駅のホームで、電車が滑り込んできた。
たつきち:
「koriymさん、今日は長い時間、ありがとうございました。……正直、頭がパンクしそうです。」
koriym:
「いえ、こちらこそ。現場の声を聞けて、私も多くの気づきがありました。」
たつきちは電車に乗り込み、ドアの前で振り返った。
たつきち:
「また、話しましょう。」
koriym:
「ええ。次はビールを飲みながら。」
ドアが閉まり、電車が動き出す。koriymの姿が、夜のホームに小さくなっていく。
たつきちは窓際の席に座り、ぼんやりと流れる夜景を眺めた。
スマートフォンを取り出し、自分が書いた過去のコードを開く。updateUser()、saveData()、handleRequest()……。
彼は画面を閉じ、そっと目を閉じた。
── 数ヶ月後
オフィスの給湯室。退勤間際の静かな時間。
コーヒーを注いでいたたつきちに、若手エンジニアが話しかけてきた。
若手:
「たつきちさん、ちょっと聞いてもいいですか?」
たつきち:
「ん? どうした?」
若手:
「さっきのPRレビューで、『この updateUser() って名前、意図が見えない』ってコメントもらったんですけど……。CRUDなんだから当たり前じゃないですか? 何がダメなんですか?」
たつきちは、カップを持つ手を止めた。
かつての自分を見ているようだった。
たつきち:
「……いい質問だね。」
窓の外を見る。あの夜の冷たい空気を、少しだけ思い出した。
たつきち:
「ちょっと長くなるけど、いい?」
若手が頷く。
たつきち:
「これは、ある人から聞いた話なんだけど──」
給湯室の蛍光灯が、かすかに瞬いた。
たつきち:
「──君は、MVCの『M』って、何の略か知ってる?」
(了)
⚠️ 重要: この議論はAIで生成された架空のものであり、登場する二人は実在の人物と一切関係がありません。また多少の不正確さを承知で強いメッセージを発信するために、制約や定義その他現実の例を意図的に単純化しています。ご了承ください。
編集後記
前編、中編を通じて、「貧血モデル」「Data Mapper」「CQRS」と議論を重ねてきました。そして後編では、それらが指し示していた本質——CRUDという思考の檻——に辿り着きました。
この物語で描きたかったのは、「正しい設計 vs 間違った設計」という二項対立ではありません。むしろ、なぜ現場は『そうなってしまう』のか という構造的な理解と、そこから抜け出すための視点の転換です。『品質 VS 開発速度』の安易な二項対立に引き込まれないことや『利益を言い訳にした設計の敗北』からの脱出です。
たつきちが最後に若手に問いかけた「MVCの『M』って、何の略か知ってる?」という問いは、前編の冒頭でkoriymが投げかけた問いそのものです。MVCの"モデル"に誤解と混乱がある一方で、歴史的に修正が不可能なほど定着しています。だからこそ対話は終わらず、次の世代へと受け継がれていく。それが、この物語の結論です。
設計に「唯一の正解」はないかもしれません。しかし、「やってはいけない、制約を見つけ出し、それを構造として実装することはできます。AI時代のコーディングはその構造の価値を改めて問いなおしているのではないでしょうか。
最後までお読みいただき、ありがとうございました。去年は私とAIとの対話でしたが、今年は私が舞台をつくり架空の人物をAIが役者として演じてくれました。最後この二人に感想を聞いてこの三部作を終わりにしましょう。来年もBEAR.Sundayをよろしくお願いします。
たつきち:
「……でも、三部作通して僕ずっと論破されてましたよね?」
koriym:
「論破ではありませんよ。たつきちさんが言葉にしてくれたからこそ、議論が前に進んだんです。『現場の声』がなければ、私の話はただの理想論で終わっていた。」
たつきち:
「そう言ってもらえると救われますけど……。でも正直、読者の皆さんに『こいつ何も分かってないな』って思われてそうで怖いです。」
koriym:
「むしろ逆でしょう。多くの読者は、たつきちさんの中に自分を見ているはずです。『分かっているけど、現場ではそうもいかない』という葛藤を。」
たつきち:
「……だといいんですけどね。あ、最後に一つ聞いていいですか?」
koriym:
「どうぞ。」
たつきち:
「結局、僕は明日から何をすればいいんですか?」
koriym:
「まずは、updateUser() に別の名前をつけてみることからでしょうか。」
たつきち:
「……そこからですか(笑)」
- 評論;「二つの世界観」三部作 批評 bt Claude (Anthropic)
- 編集後記の後記
-
誰も気にしていなかったと思いますがこれはBEAR.Sundayアドベントカレンダーの記事です。 ↩


