ドメインの全貌の理解を助けるBDDのススメ
チームにジョインしたエンジニアに立ちはだかる壁
チームにエンジニアがジョインした時、しっかり伝えるべきなのにうまく伝えにくいものの一つは、開発中のシステムの複雑なドメイン知識ではないでしょうか。
ドメイン知識は、システムがクリーンアーキテクチャなどで綺麗に作られていても、コードからはなかなか読み取りにくいものです。 私見ですが、読み取りにくい理由は、ドメイン知識がドメイン層(Entity層)という単一の層に凝縮されており、現時点でのアーキテクチャ理論として、複雑なドメインの絡まりを紐解く有効な施策が(まだ)存在していないためかと思っています。
注:クリーンアーキテクチャは、ドメイン層以外との分離と、ユースケースの明確化において非常に重要なので、私はクリーンアーキテクチャを絶賛支持しています
しかも、ドメイン知識は、ビジネスサイドや他チームからの要望により、過去何度も変更されてきているかもしれません。
重要なドメインオブジェクトは、ユビキタス言語としてチームのドキュメントに、箇条書きや表で整理されているかもしれません。 また、ドメインオブジェクトの関係を、クラス図のようなもので図示しているかもしれません。
しかし、それだけでは、チームにジョインしたメンバーには、ドメインが理解できないはずです。
それがなぜか、例を使って説明します。
まず、以下のようなユビキタス言語をドキュメン化されているとしましょう。
[eコマースサイト]
- ユーザ
- サイトのアカウントを持っている人
- 匿名ユーザ
- サイトにログインしていない人
- カート
- 購入したいものを選択し保存する機能
- キャンペーン
- 特定の商品(群)に対し、特定の期間、値段を割引くこと
- 決済確認画面
- カート内の商品をユーザが購入する前に、金額や支払い方法等をユーザが確認し承認する画面
- 決済
- カート内の商品をユーザが購入し、料金請求を行い完了すること
割と馴染みのありそうな、6つのユビキタス言語ですが、それだけでも結構複雑です。
なぜ、匿名ユーザというドメインが必要なのでしょうか? もしかしたら、まだログインしていない人には、「今登録すれば〇〇が50%OFF!」と広告を出したいので、匿名ユーザを定義する必要があるのかもしれません。
バックエンドで、決済画面で料金をユーザに示しているのに、承認後の決済直前に、また商品の料金を確認する処理が挟まっているとしたら、それはなぜでしょうか? もしかしたら、決済画面の表示から承認までに、キャンペーンの期限時刻をまたいでしまう可能性があるかかもしれません。
先の問いの戻り、なぜ新規メンバーにとってユビキタス言語の定義と関係性の図だけでは、ドメインの理解に不十分なのかの答えは、「そのドメインオブジェクトがどう使われうるのかまでは分からないため」です。 少し言い換えるなら、ドメインオブジェクト間の静的な関係性までは理解できるが、動的な(振る舞い的な)関係性をも全て網羅的に理解するのは困難なのです。
先程の、匿名ユーザや、決済時の料金確認処理が、仮にコードから推測できたとしても、より根深い問題として、ジョインした新メンバーは、「自分がどこまで無知なのかについて無知」なのです。 理解できた部分だけ理解出来てたら、それで十分なのか、の判断がつかないのです。
この話は、BDDの提唱者であるDaniel Terhorst-North氏のブログにも記載されています。
この結果、自分がドメインの部分に手をつけたことによって、到底想像もつかなかった悪影響が出る恐れを抱きながら開発をする羽目になります。 真面目なエンジニアは、そのような悪影響を回避するために、多量の時間を使って、コードを理解し、周辺の依存するコンポーネントを理解することになります。
近年のマイクロサービスアーキテクチャの場合、最悪の場合は、自分のチーム外のマイクロサービスについての理解を進めないといけないでしょう。
その結果、最悪の場合、〇〇システムは、もう誰々さんしか手が付けられない、という状況に陥り、メンテナンス上のリスクに繋がります。
では、より人材の流動性に寛容的で、持続可能なシステムを作るにはどうすべきでしょうか?
BDD (Behavior Driven Development)
これが唯一の解、と言いたいわけではありませんが、私自身が、最近転職をして非常にメリットを感じたものの一つが、BDDでした。
BDDは、ステークホルダーの視点から見た、システムの振る舞いを定義して、システムを開発することです。 TDD (Test Driven Development)が、ユニットテストを用意してからプロダクションコードを開発する、という手法ですが、それに対しBDDは、システム外部から見た振る舞いに着目します。
BDDには、主に、実例マッピングと、Event Stormingの二つのアプローチがあります。
実例マッピングは、システムが実際に、どんなデータで、どう使われて、その結果、どういう振る舞いが期待されるか、と言うことを、さまざまな関係者との議論を通して、詰めていくことです。
システムの期待する振る舞いに関して、どんなシナリオがあり、そのシナリオにおいてどんなルールがあるのか、まだ不明瞭な部分はどこか、を付箋で整理します。 そして、重要なのは、それを、開発者やプロダクトオーナー、QA、デザイナーなど、様々な関係者と、その付箋をベースに、議論するのです。 それにより、システムの振る舞いの明文化と、スポットライトを当てていなかったシナリオの気づきを得て、新たにシステムのあるべき振る舞いを定義・明文化するのです。
具体的な進め方は、こちらのスライドが分かりやすいでしょう。
もう一つのアプローチとして、Event Stormingがあります。
イベントドリブンなマイクロサービスアーキテクチャの界隈では、最近有名になってきましたが、ドメインの中で、どんなイベントが、どんなシチュエーションで、どんな順番で発生するか、を、実例マッピングの時と似たように、付箋で整理していきます。 整理されたイベントが、ドメインイベントとなり、実装に反映されていきます。
日本で(?)BDDと言った場合、一般的には、前者の実例マッピングの「明文化」を、コードとして記述するライブラリに焦点を当てた説明が多いかと思います。 Cucumberが、BDDで有名なライブラリで、大抵の主要な言語で利用できます。
Cucumberでは、自然言語に近いフォーマットで、先程の実例マッピングのシナリオを、〇〇な状況において(Given)、××が起きた時(When)、△△(Then)が起きるはず、という形式で、Featureと呼ばれるファイルに記述します。 そして、ステップと呼ばれるテストスクリプトに、Given, When, Thenのそれぞれの自然言語と同じ名前のテストメソッドを作成します。 BDDの実行時には、Featureに記載されたシナリオに沿って、順番にテストメソッドが実行されていきます。
ここでは、BDDのライブラリの詳細は説明しませんが、それに関してはググると多くの情報が拾えるかと思います。
ただ、重要なことは、単にライブラリを使うことよりも、実例マッピングやEvent Stormingのような作業的プロセスを通して、ドメインの全貌を掘り起こして記述する、ということです。
DDDに関するドキュメントと、クリーンアーキテクチャなどのドメインがしっかり独立して記述されたコード、そして、システムの振る舞いを定義したfeatureとそのテストコードが、相互補完的にドメインの全貌を明らかにします。
おわりに
自分自身、5月に転職をして、5月中旬から開発に入りました。開発中のシステムには、かなりのドメインモデルがあり、しかもその関係性はかなり複雑でした。 当然、そのシステムを、ユーザの目線でどう使われるものかは、入社後にレクチャーして頂きましたが、いざ開発しようとすると、ドメインに含まれるロジックの理由・根拠が、レクチャーされた内容やドキュメントだけでは不明なものが多くありました。 ただ、幸いそのシステムには、多くのFeatureが用意されており、今もそれに助けられています。
助けられているというのは、実作業的にもそうですが、精神的な面が大きいです。 ドメインの理解が、自分なりに進んでいると実感できるのは、エンジニアの精神衛生的に非常に重要です(誰もが自分のせいでバグの産んでしまう恐怖と闘っていると思います)。 その上で、今、DDD, クリーンアーキテクチャ, TDD, BDD, マイクロサービスアーキテクチャで、そのシステムを開発していますが、この中で、入社後一番あって(精神的に)助かったと感じたのは、BDDかなと感じています。
補足
実例マッピングと、Event Stormingの解釈の違いで、あくまで実例マッピングは、実例を掘り起こしていくことであり、そのやり方として、Given, When, Thenに付箋を付けるか、イベントに対してつけるか、という分け方として説明されているケースもあります。