こんにちは。最近Slackのカスタム絵文字作りにハマっている友野です。Holmesでサーバーサイドエンジニアをしています。
Holmesが提供するホームズクラウドは、今年8月にサービスローンチ3周年を迎えました!
これまでの支持に感謝し、これからも長く使ってもらえるようにプロダクト改善に取り組んでいます。そのひとつとして、ドメイン駆動設計(以下、DDDと表記します)適用に関する取り組みについてご紹介します。似たような状況や同じ課題を持つ誰かの一助になれば幸いです。
背景と現状
ホームズクラウドはPMF(Product Market Fit:プロダクトマーケットフィット)を経て、サービスシェア拡大のためにトレードオフスライダーをスピードに全振りしていました。Spring Bootを利用した三層+MVCアーキテクチャを採用し、ローンチ以来、機能追加・改善を繰り返し行ってきました。
日本ではCLM(Contract Lifecycle Management:契約ライフサイクルマネジメント)領域はまだ深く浸透しておらず、認知度向上のためにはとにかく市場評価する必要があり、スピード最優先にする戦略・アーキテクチャ採用は間違っていなかったと思います。
一方で、コアドメインである契約関連機能はその仕様の複雑さも相まって、コードの複雑性も増加の一途を辿り、メンテナンスコストが上がってきました。サービスローンチから3年をふりかえり、市場変化に合わせて「変更に強いプロダクト」を作ることがCTOはじめメンバー全員の共通認識になり、DDD採用を決める後押しになりました。
まずはじめたこと
採用を決めたものの、DDDとは何だろうか?というメンバーが多い状態から第一歩を踏み出すために、有識者の力が必要でした。
DDD Community JPを主宰している松岡さんに声をかけ、力を貸してくれるようお願いしたところ、快く引き受けてくれた上にライブモデリングとライブコーディングまでしてくれる運びとなりました(感謝しかありません)。それまでにメンバーは松岡さんのYouTubeで基礎知識をインプットし、モブワークに臨みます。
テキストベースのコミュニケーションをはさみながら、モブワークを2回ほど実施した後のメンバーの感想は以下の通りです。
不安に思いながらも、多くのメンバーがその効果と威力に期待を膨らませ、モチベーションを高めていきました。
戦略的モデリング
先日の記事で触れたように、社内で勉強会を重ねた上で、仕様の共通理解と深掘りを行うためにドメインモデリングに取り組み始めました。具体的には、弁護士資格を持つ社員をドメインエキスパートとしてスクラムのリファインメントに招き、モデリングを実施しています。ドメインモデリング自体初めてのメンバーが多いため、以下の書籍を参考にモデリングの進め方ガイドを作り、適宜ふりかえりを交えながら進めています。
現在リモートワーク中心での勤務体系のため、議論はオンラインホワイトボードmiro上で行い、モデリングをした後にバージョン管理のためにPlantUMLでまとめています。
そして、戦術的な設計
仕様の複雑性への対策として、ドメインモデリングだけでも一定の価値はあると考えています。これに加えて、戦術的な設計領域*1 まで踏み込むことでさらにDDD採用の効果を高め、変更に強い状態を作ることを目指しています。
しかし、既存の資産、かつ稼働しているサービスにおいて、どこから手をつけるか非常に悩ましい課題です。最初はドメインモデリングした成果を活かすために、集約をコードで表現することからスモールスタートすることに決めました。
採用するパターン2つ
集約をコードで表現するために以下の2つのプラクティスを採用します。
- ドメインモデルを反映したオブジェクトを置くパッケージの作成
- 既存テーブル構造に依存しない
Repository
+Adapter
パターン
ドメインモデルを反映したオブジェクトを置くパッケージの作成
まず何より、ドメインモデルを反映したドメインオブジェクトを置く場所を決めます。既存のパッケージ構成では、Spring MVCやSpring Data JPAのコンポーネントに合わせ、controller
、service
を中心に、entity
やrequest
などのモデルのパッケージを切っています。service
パッケージのクラスには、データモデルが渡されて手続き的に処理されるコードがあるため、ドメインオブジェクトを配置するのは適切ではありません。ドメインオブジェクトを置くdomain
パッケージを作ることにしました。service
パッケージのクラスの修正は最小限に留め、ビジネスルールに関する操作をドメインオブジェクトに移譲する状態をゴールとしています。
service
パッケージ内のクラスが担う責務- 全体の流れを構成する責務
- データの取得、永続化(を依頼する)責務
domain
パッケージ内のクラスが担う責務- ビジネスルールに基づく判断/加工/計算の責務
いわゆる三層アーキテクチャ+ドメインオブジェクトのパターンで、これは以下の書籍を参考にしています。
現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法:書籍案内|技術評論社 gihyo.jp
既存テーブル構造に依存しないRepository
+Adapter
パターン
一方で、すでに稼働しているサービスではDB構造を大きく変更することは難しく、リスクも伴います。ホームズクラウドでは、データ永続化のライブラリとしてSpring Data JPAを採用しており、Repository
インタフェースはあるものの、直接JPARepository
を実装している*2のが現状です。つまり、JPAエンティティ(DDDのエンティティではないのでJPAをつけています)を渡さないと永続化できません。これでは既存のDB構造に強く依存してしまい、ドメインオブジェクトの構造自体が既存構造に引きずられかねません。
Repository
インタフェースにドメインオブジェクトを渡したいので、JPA用のAdapter
クラスを実装して、JPAエンティティへの変換と永続化責務を移譲することにしました。これで、既存資産のJPAエンティティへの依存を切ることが出来ました。DDDの文脈でのコンテキストマップにおいて腐敗防止層という考え方がありますが、これを既存概念(データモデル)に適用した形です*3。
では、このAdapter
クラスをどこに置くかというと、また新しくinfrastructure
パッケージを追加しました。前節では三層アーキテクチャ+ドメインオブジェクトと記載しましたが、ここに関しては部分的に”依存関係逆転の原則”に従い、ヘキサゴナルアーキテクチャ(ポート&アダプター)の構造になっています。
ふりかえり
設計変更の効果と開発プロセスへの影響度合い、実装イメージの共有を目的として一部追加機能で実装を始めた段階です。結果が出るのはまだ少し先になりそうですが、既存のデータモデルに引きずられず、ドメインモデリングした結果をうまくコードで表現できていると思います。また、追加機能の実装を中心的に推進してくれたメンバーからは以下のようなコメントをもらっています。
最も複雑、かつコアドメインである契約領域への適用は、ロードマップに従って順次進めていきます。並行して、本記事で触れていないプラクティスについても有効性を確認しながら適用していこうと考えています(少なくとも、ドメインサービスはかなり悩んだ/悩んでいるので、別途記事にできればと思います)。
まとめ
戦略的なドメインモデリングと並行して取り組んでいる、コードで表現する最小限のパターンをご紹介しました。本記事で触れた以外にも多くのプラクティスがありますが、すべてをすぐにプロダクトへ適用するのは難しいものです。なにより既存資産、既存サービスへの影響を考えながら、手段と目的を履き違えぬよう注意深く進めなくてはなりません。
◯◯をしたからDDDやってます!もなければ、△△していないのならDDDとは呼べない!というのはないと考えています*4。
ドメイン知識を育てながら、愚直にオブジェクト指向プログラミングをするのが、一番のDDD成功への近道と言えそうです。
最後に
Holmesでは、今後もコアドメインに注力し、変更に強いプロダクト構築を通じて、お客様の契約業務に関する課題の解決とCLM市場拡大を進めていきます。 DDDに関して知見や興味があり、一緒にHolmesの目指す世界観を作りたい方、是非力を貸してください!
Holmesでは現在エンジニアを募集しています。 興味がある方は是非こちらからご連絡ください!
*1:集約、値オブジェクト、エンティティ、ドメインサービス等のプラクティスやアーキテクチャなどを指します。詳細は以下の書籍をご参照ください。
エリック・エヴァンスのドメイン駆動設計(牧野 祐子 牧野 祐子 今関 剛 今関 剛 今関 剛 和智 右桂 和智 右桂 Eric Evans)|翔泳社の本 www.shoeisha.co.jp
*2:厳密にはJPARepositoryインタフェースを継承していて、Springが暗黙的に実装していますが、分かりやすさを優先しています。
*3:念のため補足すると、既存資産が腐っているという意味ではありません。念のため…。