taiyoh's memorandum

@ttaiyoh が、技術ネタで気づいたことを書き溜めておきます。

最近、自分の関わっているサービスをDDDで再設計してるというお話

タイトル以上の意味はない、ただの経験談

 きっかけは半年前、自分の担当するサービスのprを後輩にレビューしてもらったとき、彼から「何書いてあるか分からないですね」と言われたことからだった。
 彼が事あるごとに言ってた「ドメイン駆動設計」について、いい加減腰据えて取り組んでいかないとますますメンテしづらいシステムになるな、という予感はあったので、どれどれ、と軽い気持ちでリファクタリングっぽいprを作ってみた。結果、その後輩から大目玉を食らってしまった。普通のエンジニアなら「おいおい」と思う話であるが、O/R Mapperの行オブジェクトをただ少し拡張しただけだったので、何の意味もないものだった。
 この理解度でDDDをやるにはレビュアーの後輩の負担があまりにも大きすぎると痛感したので、一念発起して先ずは「実践ドメイン駆動設計」(以下IDDD)を読むことに。

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

 実はEric Evansの「ドメイン駆動設計」(以下Evans)も以前購入はしていたが、概念の説明が全く頭に入ってこなかったので恥ずかしながら早々に脱落してた。が、今回は具体的に自分の関わってるサービスに落とし込むというゴールがあるので、それなりに必死だった。常に付け焼き刃ではあったが、IDDD本で得た知識はなるべくすぐにそのprに反映する形にし、後輩のアドバイスも、本を読み進めるごとに少しずつ何を話しているのかが分かるようになってきた。
 結局、DDD化第一弾となるそのprは、差分1000行ほど、業務の合間にちまちま直し続けて一ヶ月かかって漸く後輩のOKが出た。但し、「多分そのうち色々直すことになるけど、DDD flavoredな実装としてはギリギリ我慢できるレベル」という条件付き。実際、その後何度も大きく手を入れている。

 IDDD本は、付録のEvent Sourcingにさしかかったところで一旦読むのをやめた。まだしばらくESを導入できる土壌がないのと、ESをしなくてもDDDは実践できる、と判断したため。あと僕は理解が遅いので、必要に応じて部分的に何度も読み返して自分の理解を少しずつ深めている。並行して、最近はまた少しずつEvans本を読んでいる。IDDD本には書いてなかったけど重要そうな要素がいくつもあり、モヤモヤしていたところがいくつか晴れた(そしてまた大掛かりなリファクタリングをすることに。。。)。
 因みに、なぜEvans本ではなくIDDD本から読み始めたかというと、「もう少し具体的なプラクティスを知りたかった」というのはあるが、「後輩からまずこちらをおすすめされた」というのも割と大きい。僕の場合、あるパラダイムについて慣れるのに基本的に手を動かして身を切らないと学べない頭の悪い人間なので、まずIDDD本でとっかかりを作り、ある程度DDDのパラダイムに慣れたところでEvans本でより理解を深める、という流れに結果としてできたのはよかった。

 DDDの概念を腹落ちさせるまでにだいぶ時間がかかったが、腹落ちしたきっかけは、自分が「データ」を中心に考えていたというのを把握したからだった。すべてのパラメータやデータオブジェクトを同列視していて、データを丹念に一つ一つ次の処理に送り込む、ということを繰り返して一連の処理を作っていたので、その膨大なパラメータのやりとりによって本来やるべきことを見失いかけていた。データはその由来や誰が変更するかという指向性や軽重があることが段々と見えてきて、これによってエンティティと値オブジェクトの区別ができるようになり、集約を固めてリポジトリが組めるようになってきた。リポジトリへの永続化の際、そのデータの変更はトランザクション整合性なのか結果整合性なのかと考えるようになり、いくつかはドメインイベントを使ってメインの処理からは外しても問題ないとわかった。DDD化を進めるうちに、ActiveRecordパターンはDDDにとってあまりにミスマッチで混乱の元凶なので、行オブジェクトをリポジトリの外に持ち出すことはなくなった。もはや行オブジェクトを「モデル」と呼ぶ時代は(今いるプロジェクト内では)終わった。DDDベースで書いた箇所については、行オブジェクトはリポジトリ内で存在確認とキャッシュにしか使っていない。本当はもっとシンプルにしても良さそうだが、O/R Mapperはトランザクション周りの管理やクエリビルダとして優秀なので、その辺は引き続き使っている。
 エンティティとリポジトリについての理解が飛躍的に上がったのは、「idを誰が発行するのか」についてきっちり切り分けて考えられるようになったからだった。今まではidの発行はMySQLのauto_increment任せだったので、DBにデータが入らないとエンティティとして完成しなかったが、id生成器を使うことでMySQLがなくてもエンティティが作成できるので、リポジトリの責務がよりシンプルで分かりやすくなった。

 こうして戦術面でどう考えていくかがある程度見えてきたので、戦略面での「ユビキタス言語」や「ドメイン」をなぜDDDでは重視するのかというのがもっと立体的になってきた。多分Eric Evansの考え方には「ドメインエキスパートの頭の中のモデルは実はそんなにドラスティックに変わることはないが、そのモデルを設計者(=実装者)が捉えきれてないために、変更要求が実装者にとってはとても大きな出来事として捉えられてしまっている」というのが間違いなくあると思う。あとActiveRecordパターンを許すと、本来ドメイン層で振る舞わせなくちゃいけない処理を「共通化」の名目で容易にインフラ層に押し込めることができてしまうので、「知らないところで何か重要なことが行われている」というのが常態化してしまう。DDDはそうした「認識齟齬」や「ごちゃまぜ」を防ぐためのルールなんだというのが半年かかってやっと体得できた。完璧に実現できてるわけではないが。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

 PDS(Presentation Domain Separation)自体は本当に重要だと思うが、これとレイヤードアーキテクチャActiveRecordパターン「だけ」で設計するとなると指針がプリミティブ過ぎて、というかそれぞれの考え方を繋ぐものがなくて実装者の手腕に委ねる部分が大きくなるので、プロジェクト内でしか通じない記法やダーティハックがはびこることになると最近は感じる。中にははその「つなぎ」を作るのが好きで色々考えたいと思う人もいるかもしれないけど、僕はそういうところに自分の足跡を残すことに興味はない。
 設計は本当に難しい。
 ただ、何故難しいと思うのかについて、その原因の一端は自分自身にあることもわかってきた。それは、設計のセオリーを持ってないことだった。そのセオリーというのもどうやらあるらしく、最近、新規実装やリファクタリングをする際、ロバストネス分析をしてアタリをつける、というのを始めた。
 ロバストネス分析についても例の後輩が見つけてきたものだ。これは「ユースケース駆動開発実践ガイド」(以下ICONIX)に詳しい。

ユースケース駆動開発実践ガイド

ユースケース駆動開発実践ガイド

 ユースケース記述→ロバストネス分析→詳細設計(シーケンス図)をきっちりやるとベストだが、一度流れだけやってみたが、完全にウォーターフォールの開発フローになるのと、開発を始めるまでかなり時間がかかりそうだったのでやめた。現在のチーム体制のスピード感には合わない。が、仕様や実装がある程度ドキュメント化されるのと、実装フローが見える化できて事前に色々話し合いができるので、うまくつまみ食いしながらほどよい距離感で進めていきたい。
 IDDD本だと、ドメインの抽出や設計について「チームメンバーで(ドメインエキスパートも含めて)よく話し合う」くらいにしか書かれていない。単純に書籍のスコープでないから省略されているのか、手法が確立されてないので言語化されてないのかは判断できない。が、ICONIX本によると「ドメインの抽出はユースケースの分析によって行う」(←だいぶ端折っている)と書いてあり、僕はこのICONIX本を読んで自分の中のミッシングピースが埋まったと感じた。確かにDDDは「整頓のしかた」については書いてあるが、肝心の「ドメイン(層)の振舞い方」については書いてない。DDDは「ドメインの振舞い」がまず定義できてこそ、ユースケースに付随するドメイン以外の種種雑多な振舞いを整頓していく、というのが王道の流れなのでは、と思うに至った。

まとめ

 この半年の経験を通して、自分がここまで設計力がなかったんだな、というのをまざまざと思い知った。でも、気づかないままこの後の人生を過ごすよりはずっとよかった。
 何より、辛抱強く色々教えてくれた後輩には本当に感謝しかない。ありがとう。