SNSチームでのドメイン駆動設計の実践
こんにちは!グリープラットフォームでSNSの開発をしています、うきょーです!
GREE Advent Calendar 2013 6日目です、よろしくお願いします!
今回は僕が所属するチームでの、ドメイン駆動設計を実践してきた過程をお話したいと思います。ドメイン駆動設計とは何か、については簡単に要所要所で説明していきますが、詳しくは本で!また、ドメイン駆動設計そのものについての話ではなく、実践の一例となります。
スマートUIパターンからのスタート
今回僕のチームが扱っていたものはJavaScript製のクライアントアプリケーションで、APIから取得した情報を表示し、ユーザーの操作によってAPIを呼び出す、というごく一般的なものです。
ドメイン駆動設計にはアンチパターンとして、スマートUIパターンと呼ばれるものが存在します。簡単に言えば「見た目都合から設計やモデルを考えてしまった」という状況です。ソフトウェアの中核をドメインモデル、と呼ばれる豊かなモデルによって構築するドメイン駆動設計とは相容れないパターンとして、本でも紹介されています。
クライアントアプリケーションでよくあるのが、あるページに対して必要な情報をAPIが返す、という設計ですが、これが多くの場合でスマートUIパターンになってします。とはいえ、一度にAPIをたくさん呼び出すのもパフォーマンス的に問題があったり、現実的にはAPIもすぐにすべて変えるというわけにもいかないので、この辺りは実装の段階で「腐敗防止レイヤー」などを設けて解決することになります。ただ、ドメインモデルを考えていく段階では一旦忘れることが大事かな、と思います。
スマートUIパターンを悪者のように扱っているように見えますが、実際はそうではありません。適材適所といいますか、本当にシンプルなアプリケーションであれば、スマートUIパターンの方がうまくいくケースが多いと思います。対してドメイン駆動設計は、よりアプリケーションが複雑化したときに、その真価を発揮します。
僕たちのアプリケーションは、仕様の追加が重なり、スマートUIパターンでは今後支えきれなくなっていくと感じたので、設計の方向性としてドメイン駆動設計を試してみよう、ということになりました。
ドメイン駆動設計のインプット
まずはドメイン駆動設計をメンバーに知ってもらうところから始まります。とはいえ、最初にすべてを知ってもらってから始めることも不可能ですし、僕自身も入門に近いレベルだったので、走りながら学んでいく感じでした。
比較的分かり易く、手をつけられるところから始める
最初にメンバーに説明したのは「ユビキタス言語」でした。簡単に言えば、関係者の間で決められた、物事に対する「名前」です。ドメイン駆動設計で最も重要なものがこれで、仕様やユーザーストーリーといったものを「ユビキタス言語」を定義しながら、分解し「ユビキタス言語」を使って話し合い、「ドメイン(アプリケーションのことだと思ってよい)」に対する理解を深めていく、というのがドメイン駆動設計の本質だと思います。
ただ、最初からこれが出来るのか、と言われると当然そうではありません。既存のアプリケーションに大きく手を入れる、ということも重なって、僕らが最初に取り組んだのは「既存の曖昧な言葉を取りさらって名前を揃える、要素を整理する」というところでした。これが後述の世界地図を作る、というところです。
ドメイン駆動設計ワークショップ
平行して何回かワークショップを行いました。こちらはメンテナンスしているアプリケーションとは全く別で、本に出てくるような過程を小さいアプリケーションで1から体験してみよう、という企画です。エンジニアだけでなく、ディレクターも参加して行いました。
ワークショップで一番重点を置いたのは「ユビキタス言語を使って説明できるか」でした。チームでモデルを設計したあとは、他のチームに向けて図を使ってそれを説明します。この時にチームのメンバーも、他のチームも、ユビキタス言語を使って説明出来ているか?という点を良く見ておくとよいでしょう。質問や突っ込みをするときも、そのチームが定義したユビキタス言語を使って会話します。
予想外だったのは、図を書くためのツールとして、大きめの紙、付箋、ペンを用意していたので、僕はモデル図っぽいものが出てくるのかと予想していたのですが、実際はそうでないものが多かったことです。しかし、それで説明が出来ないかといえばそうではなく、むしろそちらの方が意図が伝わり易かったり、エンジニアだけでなくディレクター側の理解も深まっているようでした。予想外の展開ではあったのですが、どちらかと言えばユビキタス言語を使って説明出来る、という点についてはモデル図よりも自由な図の方が有利なように思いました。
ですので、実際にワークショップをやってみよう!と思う方は、あまりモデル図にこだわりすぎない方が成功するかもしれません。
毎回いろいろなアプリケーションが生まれましたが、自動販売機のモデルを考えてみた回は、ドメインの大きさとしても丁度よく、盛り上がりました。自動販売機と言っても、チームによって考えている部分が異なり、お金を入れて、商品を手に入れるまでの全体のフローを考えるチームや、商品の組み合わせ(例えば、コーヒーにクリームを入れるか?)を考えるチームもありました。
ワークショップは大きめの紙、付箋、ペンがあれば不自由なく開催出来るので、お菓子などをつまみながらワイワイとやるとよいと思います。
現状の世界地図を作る
少し話が戻って、「既存の曖昧な言葉を取りさらって名前を揃える、要素を整理する」の実践編です。現状のアプリケーションで使われている名前を取り出して、それらを整理していく、というのが大まかな内容です。こちらもワークショップと同じ様な形式で、ホワイトボード、または大きめの紙、付箋、ペンで行いました。また、この作業は1ヶ月ほど掛けて、チーム全体で少しずつ進めていきました。
まず最初に、機能単位で島を作ります。例えばグリーのSNSであれば、ストリームやコミュニティ、メールといった単位です。それぞれの島で今ある概念を出していきます。最初は何も気にせずに要素をブレストくらいの気持ちで出していきます。大体の要素が出切ったところで、それぞれの言葉について、一人一人の認識を確認してみます。僕のチームの場合は、半分くらいは認識が一致していませんでした。
例えばストリームには、EntryとContentsという名前が共通して存在はしていたものの、
- ストリームのContentsは複数のEntryで構成される
- Entryの内容としてContentsがある
- Entryってそもそも・・・
などなど、意見が分かれました。おそらく多かれ少なかれこのような状況が必ず生まれます。一度立ち止まって、認識を合わせていきます。
それぞれの島で要素がある程度揃ってきたら、次に共通する部分を集めていきました。ドメイン駆動設計の言葉を借りると、アプリケーション内での「共有カーネル」です。分かり易い例では、ユーザーは何処でも必要な概念ですね。
とはいえ、それぞれの島で使われているユーザーは、それぞれの島に必要な要素を持っているため、概念として共通しているところと、そうでないところを分離します。ここでもおそらく、言葉が曖昧になります。実際にあったのは、ユーザーの「名前」と呼んでいたり、「ニックネーム」と呼んでいたり。他には「n時間以上前」のような時間の丸め処理にも、種類によって名前を与えたりしました。
(度重なる利用でそろそろ模造紙がヘタってきています・・・・)
共有カーネルが出揃ってきたところで、少しずつ実装へ移す作業へ移行していきました。
実際にコードに落としていく
コードに落としていく段階で一番重点を置いたのは、「定義している言葉とモデルを使う」という点です。最初にコードに落とす段階では、「少しくらい実装上の問題があってもあまり気にしない、まずは言葉を揃える」ことにしました。途中いくつか問題も起きましたが、その度に一旦立ち止まって話し合おう、というスタンスで進めていきました。
それと同時に、「レイヤー化アーキテクチャ」について少しずつ知識を深めていきました。「レイヤー化アーキテクチャ」はアプリケーションは4つ(3つ)のレイヤーから成り立っているとし、それぞれのレイヤーに責務を隔離、疎結合にし、設計を明確にしよう、というものです。構成としては、一般的に以下の4つに分かれます。上から、
- ユーザーインターフェイス層
- アプリケーション層
- ドメイン層
- インフラストラクチャ層
となります。
下の以前に行ったプレゼンテーションの図を見てもらいたいのですが、この様に基本的に上から下へのみ依存していきます。下の層から上の層へは通知やコールバック、といった緩いつながりを持って実装されます。
これまでの話の中で定義してきた言葉やモデルは、そのほとんどが「ドメイン層」に該当します。
それぞれの層の役割についてはプレゼンテーションを何枚か進めてもらえると、なんとなく雰囲気が掴めるかと思います。ドメイン層を充実させつつ、ユーザーインターフェイス層とアプリケーション層で肉付けしていき、インフラストラクチャ層がそれを支えます。
僕らのチームでは、ユーザーインターフェイス層とアプリケーション層の区別をほとんどしない形で実装しています。クライアントアプリケーションの場合は、この2つがほとんどの場合で強結合であることがその理由です。
フレームワークの選択
当初はUIフレームワークとしてBackbone.jsを使おうと思っていたのですが、言語としてTypeScriptを選択したことも相まって、「Backbone.jsやSpineなどのCoC系フレームワークを使ってもあまり嬉しくない」という状況になってきました。ドメイン駆動設計では、「モデル」を中心に置いた設計になるのですが、Backbone.Modelなどはここで言うモデルとはまったく別物です。上のプレゼンテーションにも少し登場していますが、Backbone.Modelはどちらかと言えばアプリケーション層に存在する、いわゆるビューモデルであって、ドメイン層を表すための物とは性質が異なります。
そこで、Backbone.Viewとほぼ互換のある簡単なViewライブラリを作り、それ以外はプレーンなTypeScriptのクラスを使って実装していく、という方針を取ることにしました。
起きた変化の話
ドメイン駆動設計に移行していく過程で一番びっくりしたのは、「テスト書きやすくなりました」とメンバーに言われたことでした。テスト辺りの環境の整備も同時に行ったので、それも貢献しているとは思うのですが、モデルの目的が明確になっているので、テストも格段に書き易くなったと思います。ほとんどテストがなかった旧アプリケーションが忘れ去られたかのように、日々テストが書かれていますし、テストの質も上がってきています、いいことですね。
区切りと成長
そんなこんなで実装も進み、一旦のリリースを無事に迎えることが出来ました。
ここまではほとんど、「既存の曖昧な言葉を取りさらって名前を揃える、要素を整理する」に注力してきました。完全なドメイン駆動設計(?)にはまだまだ遠いのですが、メンバーもドメイン駆動設計に興味を持ってくれており、駆け出しとしては上々だなと思っています。
目に見えるチームの成長として、レビューの質がものすごく上がったように感じます。今までは悪い言い方をすると「小手先のレビュー」が多かったのですが、最近では実装されたコードがどうか、という話よりも、
- 「このモデルの責務がおかしいんじゃないか?」
- 「もっと適切な名前を付けれるんじゃないか?」
- 「ここにもっと何かある気がするし、それを定義すればうまくいくんじゃないか?」
- 「絵で書くとこんな感じ」
という内容になってきていて、より本質的な部分のレビューが全体で出来るようになってきたと感じます。
今後
少しずつ出来るようになってきたドメイン駆動設計、まだまだ奥が深いです。ここからの展望としては「ユビキタス言語を使いこなす」でしょうか。今は「使う」の状態なので、さらに先の「使いこなす」をチームで目指してみたいと思っています。
まとめ
そんな感じで、僕らのチームでのドメイン駆動設計の駆け出しを晒してみました。これからやろうと思っている皆さんの力に少しでもなれば嬉しいです!
次は @yoyapp さんです!