ライブラリは作るものではなくできるもの
#ソフトウェア設計 #ライブラリ #値オブジェクト #ドメイン駆動設計(DDD) #関心の分離
#共有する
https://x.com/mizchi/status/1905921174930137530?t=mnmnii6J2xHx1sOJjFZwLw&s=19
雑感:ライブラリは「発見」するものである ~ @mizchi氏の発言から考えるライブラリ設計とVOの扱い
先日、X.comで @mizchi さんのこんな発言を目にしました。
ライブラリは作ろうとしてできるもんじゃない。気づいたらできてる。
仕事のコードからドメイン依存を抜いた汎用処理を切り出したものを「発見」するのがライブラリを作る能力で、オープンにするかどうかはともかくリファクタ兼ねて社内ライブラリみたいなのは常に切り出しておく作業は必要で、あとは会社によってそれがオープンになるかどうか。
これ、ソフトウェア開発の本質の一部をすごく上手く捉えているな、と感じました。特に「ライブラリは作ろうとしてできるもんじゃない。気づいたらできてる」「『発見』するのがライブラリを作る能力」という部分。トップダウンで設計書を書いて作るというより、日々のコードの中から「お、これって他の場所でも使えるな」と汎用的な部品を見つけ出す、ボトムアップ的なプロセスこそがライブラリの本質だ、と言っているように聞こえます。
そして、その「発見」の鍵が「仕事のコードからドメイン依存を抜く」ことだ、と。これ、非常に重要な指摘ですよね。
レガシーからの脱却と「便利ライブラリ」の罠
実は今、私の周りでは、長年動いてきた「泥団子」のようなレガシーアプリケーションから、DDDやクリーンアーキテクチャといったモダンな設計思想を取り入れた新しいパラダイムへの移行を計画している真っ最中なんです。
そのレガシーアプリには、「ライブラリ」と称して、特定の計算処理やデータ操作などがまとめられています。一見便利なんですが、中身を見るとがっつりドメイン知識(特定の業務ルールやデータ構造への依存)を含んでいて、それがドメインの境界を越えてあちこちから呼び出されている…。まさに密結合の温床です。
移行にあたって、一部の開発者からは「今のライブラリをどうやって新しいシステムに移植しようか?」という声も聞こえてきます。でも、私はむしろ、この「ドメイン依存まみれの便利ライブラリ」こそが、これまでのシステムの保守性や拡張性を蝕んできた元凶の一つだと考えています。新しいパラダイムでは、関心の分離を徹底し、適切にモジュール化された、疎結合な世界を目指したい。
そんな状況で @mizchi さんの発言に出会い、「これだ!」と思ったわけです。この「ドメイン依存を抜く」という視点は、今の我々の課題に対する処方箋になりうるし、ライブラリというものへの認識を新たにする良いきっかけになるはずだと。
なぜ「ドメイン依存を抜く」必要があるのか?
とはいえ、「ドメイン依存を抜く」と言われても、ピンとこない人もいるでしょうし、そもそもその意義を理解してもらえないかもしれません。ましてや、それを実践するのは簡単ではありません。
なぜ、ライブラリからドメイン依存を抜くことが重要なのか? それは、真の再利用性、テスト容易性、そして変更の影響範囲を局所化するためです。
ドメイン知識を含んだライブラリは、特定の状況(コンテキスト)でしか使えません。無理に他のコンテキストで使おうとすれば、予期せぬバグを生んだり、使う側で複雑な条件分岐が必要になったりします。テストするにも、そのドメイン知識や前提となるデータ状態を準備する必要があり、非常に手間がかかる。そして何より、ドメインの仕様変更がライブラリに影響し、ライブラリの修正が思わぬドメイン機能に影響を与える、という密結合による「修正地獄」を生み出します。
DDDでいうところのドメイン層の知識が、インフラ層やユーティリティ層(ライブラリが置かれがちな場所)に漏れ出すのは、まさにこうした問題を引き起こすからです。関心の分離を徹底し、依存関係を適切に管理することこそが、変化に強く、保守しやすいシステムを作るための「必然」なのです。
「おかしなライブラリ」を再生産しないために
では、どうすれば「ドメイン依存を抜く」ことを実践し、おかしなライブラリの量産を防げるでしょうか? いくつかのアプローチが考えられます。
1. 「なぜ?」を具体的に共有する: 上述したようなメリット・デメリットを、具体的な失敗事例などを交えながらチームで共有します。
2. 「発見」のための問いかけ: 「この処理、全く違う業務システムでも使える?」「特定のDBテーブルを知らないと動かない?」「もっと汎用的な名前にできない?」といった問いかけで、ドメイン依存に気づく手助けをします。
3. ライブラリ化の判断基準: 「共通処理=即ライブラリ」ではなく、「ドメイン非依存か?」「真に汎用的か?」「安定しているか?」といった基準を設けます。
4. レビュー文化: 新規作成・移植時には必ずチームでレビューし、「本当にドメイン依存は抜けているか?」「ライブラリ化の必然性は?」を議論します。モブプロなどで一緒に「発見」プロセスを体験するのも良いでしょう。
5. アーキテクチャによるガードレール: レイヤー化や依存関係ルールを明確にし、構造的にドメイン知識の漏洩を防ぎます。静的解析ツールの導入も有効です。
要は、Why(なぜ) を伝え、How(どうやって) を示し、Practice(実践する場) を提供することが重要だと考えます。
値オブジェクト(VO)のライブラリ化 – 悩ましい境界線
さて、ここで少し悩ましい問題が出てきます。「値オブジェクト(VO)」の扱いです。金額計算(Money)や区分値バリデーション(OrderStatus)など、ルールをカプセル化したVOは非常に便利で、再利用したくなります。これらをライブラリ化するのはどうでしょうか?
VOは純粋なユーティリティ関数と違い、それ自体がドメインにおけるルールや不変条件を表現しています。ある種の「ドメイン知識」を含んでいるわけです。
ここで、「ドメイン依存を抜く」を「特定のBounded Context固有の依存を抜く」と解釈するのが鍵になりそうです。
ライブラリ化を検討しても良いかもしれないVO:
Money(通貨と金額)、DateRange(期間)、EmailAddress など、複数のBounded Contextで共通して使われる基盤的・汎用的な概念。これらはDDDの「Shared Kernel」に近いかもしれません。
ライブラリ化を避けるべき/慎重になるべきVO:
OrderStatus(特定の注文プロセスにおける状態)、ProductCode(特定の製品体系のコード)など、特定のBounded Contextに強く紐づく概念。これらはそのContext内に閉じ込めるべきです。
「住所」(Address)のように、名前は同じでもContextによって意味やルールが微妙に異なる概念。安易な共通化は、複雑さや意図しない結合を生みます。
たとえライブラリ化するとしても、そのVOには特定のContext固有のロジック(例: 特定税率での計算)は含めず、真に汎用的な不変条件(例: 金額はマイナスにならない)に留めるべきでしょう。
結論として、VOのライブラリ化は「慎重に」判断すべきです。 基本は**「各Bounded Context内で定義し、育てる」**。ライブラリとして共有するのは、そのVOが本当に複数のContextを横断する普遍的な概念であり、明確なメリットがあると判断できた場合に限るべきではないでしょうか。「便利だから共通化」ではなく、関心の分離、凝集度、コンテキスト境界といった設計原則に基づいた「必然性」 がそこにあるかを問うことが、健全な設計への道だと考えます。
ライブラリは確かに「発見」するものですが、その発見の質を高め、適切に管理していくには、こうした設計原則への深い理解と、チームでの丁寧な議論が不可欠なのだと、改めて感じさせられました。