DDD以外の設計手法についてご教示いただきたく、DDDの主張をある程度正確に理解した上でDDDをこき下ろしているイメージの強いくまぎさんに質問させていただきました。

最近はソフトウェアの設計について調べると、DDDについての記事ばかりで辟易する一方、私がエンジニアになった頃にDDDに勢いがあった影響もあって私自身DDD以外の良い設計とされているものを知らず、DDDに胡散臭さを感じつつもDDDの考え方にとらわれている、毒親の影響を受けた子供のような状態から抜け出せずにいます。

その最たる例がリポジトリパターンです。

よく依存性の逆転・DIと一緒に語られますが、くまぎさんがおっしゃる通り余計にインターフェースを切るのはイケてないと感じます。また、DI抜きにしても、リポトリパターン由来の様々な問題(N+1やバルクアップデート、管理画面用のメソッド生やしたくなる問題など)に対する解決策として提示されている手法をみて、自分で問題をでっち上げているように見えます。その一方で、ドメインのモデルを集約という単位で、リポジトリを介してDBに出し入れするのは便利そうに感じます。リポジトリパターンを使わずにドメインのモデルの入出力が書き散らされるとモデルにプロパティを追加するときなどにあちこち修正の必要が出て面倒です。

そもそもリポジトリパターンのようなDBとのやりとりをするところにドメイン知識を持ち込まないという考えに無理があるのでしょうか?別の方への回答に「DDDの人たちが言うようなドメインロジックを中心に据えてストレージをリポジトリやインフラ層に追い出したりするアイデアには反対です。疎結合を有難がる風潮が念仏のように唱えられがちですが、データはアプリケーションの本尊そのものであって、脇に置いたりあとから気軽に挿げ替えたりするものではありません。言い換えるとそこに疎結合を求めてはいけません。」と書いていましたが、くまぎさんがアプリケーションを書くときは、DBとドメインロジックをベッタリさせて書いていますか?ドメインロジックのテストを書くときはDBを立ち上げてデータを入れてテストしているのでしょうか?

くまぎさんが実践しているプラクティス等あればぜひご紹介いただきたいです。

そ、そんなにこき下ろしているでしょうか…?

プログラミングにおいて教条的に何らかの思想を上から振り下ろす事に対して違和感を持っているだけのつもりで、代わりに何らかの思想を振り回す事が良いとも思っていません。

クリーンアーキテクチャの書籍では「データベースなんかは玄関のドアノブのパーツぐらいの重要度しか無いのでいつでもドアノブを取り替えれるようにだけしておいて本題に集中しろ」みたいな事を書いていたので流石にそれは無茶というか巨大プロジェクトがエアプなのかって感じる事はありました。改めて主張を整理したいと思います。

まずDBとドメインロジックをベッタリさせて書いているか?という問いですがYESです。特にGoogleの場合は「SpannerからMySQLに移行しよう」みたいな話はまず出てこないですし、仮に移行するならそういう独立したプロジェクトとしてチームで取り組む規模の話になることでしょう。またユニットテストでも可能な限り本物に近い環境を使うように努力せよという指針も公開されています。

Increase Test Fidelity By Avoiding Mocks

This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT e...

testing.googleblog.com

https://testing.googleblog.com/2024/02/increase-test-fidelity-by-avoiding-mocks.html

ドメインロジックがベッタリしていると困るケースとしては他にロジック自体の意味がわかりにくくなるというケースがあります。ただこちらは純粋にコードの抽象度の話であって、不慣れな人がコードを書いたら抽象度の高い業務ロジックとDB実装側の都合から来る個々のコードが入り乱れて読みにくくなるのは「DDDを適用しなかったせい」ではなくて「コードの再利用性が低いせい」です。

オブジェクト指向や他のパラダイムの事を神格化して聖典のように崇める人もいますが、一介のコード書きから見たらそれらすべて「コード整理術」の一部でしかありません。膨れ上がり続けるソースコードと向き合って増加し続ける複雑さをどう整頓するかがすべてです。整頓することの恩恵を部屋の掃除程度にしか思ってない人もいますが、コードが整理されるとバグ修正も機能追加も「ここのモジュールが本来ではこうするべきだったから」という形で責任が分割されるのでプログラミングの難度が大幅に下がるという効能があります。言い換えれば、将来のプログラミングの難度を下げる事ができるのが良い抽象化、そうでないならどんなプラクティスも自己満足です。究極的には例えば通販なら注文ロジックの最上位が「店舗.発注(注文票)」ぐらいになっていれば「これからはこういう条件の注文が来た時は却下したい」等と仕様追加があった時にどこに手を加えるべきか自明になって便利、ぐらいの簡単な話です。

ここにおいてすべての値を値オブジェクトにラッピングするのは例えるなら整頓用の箱を大量に用意する事で部屋が整頓できると信じるようなものであって有害ですらあります。

クリーンとかオニオンとかそれぞれの言葉でロジックを階層化することで複雑さを閉じ込めたと主張する人は多いです。中にはただ単に教典にある形に近くなった事を喜ぶ人もいるかも知れません。僕がプログラミング初心者に何度も強調して伝えているのは「再利用する予定のないロジックでも再利用性を重視したコードを書け」という指針です。レイヤーを分ける事自体は善にも悪にもなりますが、そのレイヤー分けの良し悪しを決めるのは「直交性」です。レイヤーごとに責務が明らかで凝集性が高いコードを…などと説明してもピンと来ない事が多いですが「仮にこの部分だけを別の目的で使いまわすならどんなインタフェースが露出していれば使いやすいか、を考える事が自ずと責務の分割と凝集性を高めてくれます(最後はセンスの問題になりますが…)。再利用性を優先することが直交性を高める為の近道である、というのはプログラミングを始めたばかりの自分にも繰り返し伝えたい直感に反する経験則です。

再利用性を優先した結果、リポジトリパターンと呼べる形にコードが落ち着く事自体はそんなにおかしな話ではありません。アプリロジックという究極的には再利用性皆無の極北からグラデーションのように段階を経てOSやDBのAPIという普遍性の塊までの間をどのように整理区分するかがプログラマに課せられた仕事であって、聖典を振り回すような場所ではないと僕は考えています。

アプリのコードの優れたアーキテクチャには究極的には未来予知が必要になります。例えば「将来的にここに仕様変更が来ると思うので拡張余地を残した」といったドメイン知識の注入が効くケースは大いにあります。ただしそれが当たったからといってそのプラクティスがどこでも通用する物であることは意味しません。重要なのは将来の変更に対して頑健であることで、将来の変更を助ける方法は小難しいテクニックを使う事とは限りません。特に最近始まったばかりのサービスというのはプロダクトマネージャにすらどこに何が起きるかわからないものですから、その内部は煮え滾る溶岩のように柔軟であるべきです。拡張性やエンバグのしにくさ(セキュアコーディングとか言いますよね)よりもコードの書き直しやすさと消しやすさに重点を置いています。拡張性ではない書き直しやすさというのは説明がしにくい指標ですが、およそ消しやすさとほぼ同義です。アプリ側の都合で数千行のコードがまるっと要らなくなることはザラに起きますが、本当にその数千行を消して良いかを判断するパッチを作るとしてその複雑さが低いほど消しやすいコードです。やたらとインタフェースを切って拡張余地を量産する場合、実際に不要だった拡張余地自体を消すのはどこから依存されているかわからなくて追加の数倍の神経を使うので、そうならないように自信を持って消し飛ばしていけるようなコードを心がけて書いています。ある程度サービスの概形が固まってきてから内部を冷え固まらせていく作業は新機能追加やデバッグのタイミングでコードの再利用と称してゴリッと書き換えて行けばよくて、人によってはこれをリファクタリングと呼ぶ人もいるでしょう。

長くなったのでまとめると

  • あらゆる教条主義は基本的に嫌いだけど最終的に似るのは別に良い

  • 凝集とか難しい言葉を使うより再利用性を意識する方が直交性に効く

  • 依存が少なくて消しやすいコードは正義、コードが若い時ほど消しやすさを優先

こんな感じになります。プログラマごとに違う考えがあると思うので他の意見も聞いてみたいですね。

24日24日更新

利用規約プライバシーポリシーに同意の上ご利用ください

熊崎 宏樹さんの過去の回答
    Loading...