DDD入門とLaravel

DDDとLaravelについて

先日、主催しているコミュニティで久しぶりに20分トークさせてもらいました。
タイトルはこのエントリと同じようなもので DDD入門とLaravelアプリケーション です。

laravel-meetup-tokyo.connpass.com

資料だけ公開してもミスリードになりそうなため、公開はしませんが内容を詳しく書いていきます。

あくまで対象はDDDに入門してみたい、という方やDDDって実装パターンでしょ、と思っている方向けです。
すでに実践、導入している方向けではありませんのであしからず。
複雑にならないようにわかりやすい文言だけで書いていますが、そうじゃないかもしれません。

前置き

タイトルからわかるようにLaravelなどを使ってある程度のOOP的なテクニックを学び、
ある程度不自由なく実装ができるようになると
アプリケーション設計などのソフトウェア的な探究心が強くなります。

そこでDDD(ドメイン駆動設計) というキーワードを目にすることが多くなり、
そこで実装例としてあげられるレイヤードアーキテクチャやさまざまなOOP的なテクニックに興味が沸き、
習得しようと多くの方が思うわけですが、
このドメイン駆動設計という言葉が一人歩きをしてしまい、言葉のインパクトや
エリックエヴァンスの書籍のイメージ、ネット上にあるさまざまな記事から実装パターンだけにフォーカスしてしまい、
DDDパターンで実装LaravelでDDDを実装
またはリポジトリパターンを取り入れればDDDというような話がよく出てきます。

必ずしもLaravelというわけではありませんが、世間一般的なアプリケーションフレームワークだと思ってください。

これは全く本質的なことではなく、
間違った理解で止まってしまうのは非常にもったいないポイントです。

DDDが全てにおいてずば抜けて素晴らしいもの、というわけではありませんが、
どういうものなのか、というのは知っておく必要があります。
という背景があるなかで、少しでも入門のための知識と間違った理解をしないようにということで。

DDDってよく聞くけどなに?

まずはDDDについてよく聞かれる質問だったり、ネット上でよくみるものですが
以下のようなものがあります。

  • DDDってアーキテクチャで層に分かれていればいいんだよ!
  • DDDで実装するんだけどデータベース肥大化
対策どうやるの?
  • MVCでDDDはできないのでは?
  • DDDで実装したけど全然楽になりません

上記のような内容をよく聞かれたりしますが、
これらは実はDDDについての認識が間違っているために出てくる質問です。

まずDDDとは、日本語でドメイン駆動設計と訳されるものですが、
早い話、実装パターンやアーキテクチャといったレイヤの話ではなく、
ソフトウェアの開発スタイルの一つ です。

問題解決領域つまりドメインに対してどう分析していくか、というところが主になるものです。

では、この問題解決領域とは何を指しているのでしょうか?
ビジネスロジックを指す、という方もいますが
開発者のほとんどの方が会社に所属していたり、
もしくは特定のサービス/アプリケーションの開発に参加しているフリーランスの方や
SIなどの形態だったりと様々だったりしますが、
会社(所属していたり派遣だったりで参加している企業) は世の中に対してどういう問題解決を行いたいか、
という大きな目標だったり思想が必ずあると思います。

たとえば自分が今所属している会社でもあるスターフェスティバルでは

スターフェスティバルは、
「ごちそうで 人々を より 幸せに」を企業理念に掲げ、レストランの中食ビジネス参入支援、
および、フードデリバリー事業を展開する会社です。
レストランや製造工場などの製造パートナー、また、配送パートナーと連携し、
「製造」以外の部分にあたる、「商品開発」「販売促進」「受注」「配達」「料金回収」 までのすべてを引き受けるビジネスモデル

とありますが、
手短にいうと、これが解決したい問題領域(ドメイン)であり、
これに基づいてサービスがいくつか展開、その中にアプリケーションが存在します。
問題解決をするにあたって、これらを 実現するために登場する利害関係者や概念などを整理し、
分析して共通で理解できる概念モデルを導き出す、そしてそれらを起点にして開発に入っていく
わけです。

こういった話は例えば半期に一度全社員集会などがあって社長などから共有されたり、
ビジネスチームから聞くこともあるでしょう。
実はそのタイミングで、「長い話をきくのはウザいな、どうでもいいや」的な姿勢でいると
DDDを実践していくためのヒントや概念が欠落してしまうので、
しっかりと聞き、理解することが非常に大事なわけです。
逆にそこに興味が湧かないと入門・実践していくのが難しい、ともいえます。

DDDに含まれないもの

人によっては違う、というものもあるかもしれませんが、
多くの企業などが掲げる問題の中に非機能要件はありません。
つまり実装言語やフレームワーク、データストレージ、キャッシュやアプリケーションアーキテクチャ
といったものは問題解決領域外のものになります。
解決のために開発者が利用するものではありますが、これらは通常含まれません。

が、例外として言語やミドルウェアやソフトウェア自体をサービスとして提供していたり
それらの領域で活動している団体・企業は除きます(Apache Foundationとか)

DDDに入門するならば

どういうものかざっくりとした概念的なものは多少理解できたと思いますが、
実際に取り組んでいくときの考え方などは後述するとして・・
まずは、どんな言語とどんなフレームワーク、データベースを使って、どういうアーキテクチャで実装するか、
という考えをまずは捨てましょう。

分析したものを実装に落とし込みやすい実装パターンや、言語というのは確かにありますが、
これは本質的なものではありません。

チームでできる範囲の実装方法を採用してもいいでしょう。
エリックエヴァンスの書籍をひたすら読み、
自分たちのアプリケーションをそれに寄せすぎるのも
ドメイン駆動とは遠くなる可能性があります。
(内容を理解してからの話ですよ。まずは読んでおきましょう)

多くの方が経験あると思いますが、新しい機能やサービスの概念などの話をきくと
データベースがここにこういう状態である、だったりこういうテーブル構造にして、
APIがこういうレスポンスで、というように実装するときにどうするか、
ということが頭に浮かぶと思います。

全く考えないというのは難しいかもしれませんが、ここから始まってしまうと
ドメイン駆動ではなく、データベースファーストやデータベース駆動設計だったり、
もしくは他の概念だったりが先行してドメインが主ではなくなっていきます。

なので浮んだとしても全くの別物だと認識しておいた方がいいでしょう。
ここが一番難しいポイントかもしれませんが。

そして書籍やネット上の記事などで目にすることが多いと思いますが、
会社のビジネスモデルやサービス仕様、
カスタマーサービスやビジネスチームの話す内容を理解することです。
アプリケーションレベルの仕様ではなく、マネタイズなども含めて実現したいことを知るのが良いでしょう。

これを実践する方法はいくつでもあります。
カスタマーサービスに加わって業務を体験したり話をよく聞いたり、

企画会議に参加したりといったことができます。

DDDを実践していくには

時代の流れで利用者などが変わっていき課題解決対象が多少変わったり、
当然退職や編成などでチーム構成が変わるため、継続的にコミュニケーションや分析を行う必要があります。
一度やればいい、というものではありません。

分析した結果実装、というフェーズになるわけですが
残念ながらいくら本やネット上で様々記事を読んでも、完璧にコードなどにうまく反映できるものではありません。
当然みなさんの所属する会社のビジネスモデルは本や記事にある例で済む単純なものではありませんし、
ビジネスモデルに変化があれば変わっていきます。

コードに落とし込むときに、実はこうではないか?ということも日常茶飯事であります。
とにかくトライアンドエラーを繰り返し、洗練させていくしかありません。
これはどんな達人であっても絶対にそうだと思います。
一度作ったら終わり、というものでもありません。

入門して実践していくには
とにかく関わるチーム全体で企業理念などの概念からドメインモデルを導き出し、
全員で課題に対して同じ認識と同じ言葉で会話できるようになること
です。

やらないように意識したいこと

これらの概念の分析などをすっ飛ばして、実装パターンだけに飛びついてしまうということは
概念にそったクラス設計やカプセル化にならず、実装者都合のものになってしまいます。
抽象レイヤが導入されたことによってテストがしやすくなる、かもしれませんが、
これはDDDではなくソフトウェアの一般的な問題解決方法の一つを採用しただけにすぎません。

所謂軽量DDDと呼ばれるもので、ビジネスモデルにも沿っておらず、
インターネットなどで見聞きしたカッコ良さそうなパターンを採用し、
短期的にはテストが書きやすいなどのメリットはあるかもしれませんが、
チームでレイヤの分け方くらいしか共通認識がないため簡単に破綻してしまいます。
ドメイン貧血症などもあります。

とはいえこれじゃダメだ!と体験することができるのもこの軽量DDDと言われるものでしょう。
失敗して得ることの方が大きいです。
おそらく5回くらい失敗すれば身を持って学ぶことができます。

こうした実装をすることで発生してしまう例を、境界付けられたコンテキストで紹介しましょう。

境界付けられたコンテキスト

言葉だけで見るとなかなか難解な境界付けられたコンテキストですが、
理解してしまえば怖くはありません。
目の前にある複雑なものも分析がしやすくなるでしょう。

あまりよい例えではありませんが、わかりやすくいうと
これがわからないと分析して導き出す主役たち、
つまりエンティティやバリューオブジェクトを区別して見つけることができないと思います。

ユビキタス言語などにも通じますが、
みなさんは同じ言葉を使っているようで
微妙にあの人とは認識が合わない
そんな経験ありませんか?

例えば昔の話などで構いませんが、友人たちが昨晩TVでみたアニメの話をしていたとします。
自分は原作を読んでいて大体の話を知っているわけです。
このためTVで観なくても大体のことは理解しています。
友人たちの会話に参加して問題なくその話題について話ができました。
ただどうやら若干原作と設定や背景が異なるものがあるようです。
自分自身は原作の知識で話、友人たちはTVで観た知識で話をして通じることは通じますが若干の違和感があります。
後日TVなどで観てみると同じ名前でちょっと違う、実は違うキャラだったことが判明しました。

アニメなどはほとんど観ないのでこうしたケースはあまりないのかもしれませんが、 こういった些細なことは日常にたくさんあります。

仕事でもあると思います。
例えばユーザーについて話しているビジネスチームがいて、
話に参加したところ、エンジニアとしてはDBに存在するユーザーの行のことを想像して聞いていると、
実は特定のステータス(有料課金者だったり)を持っている人のことを指していた、だったり
ある言葉がエンジニアチームと共通したキーワードがありますが、
違う意味で他のチームが使っていてそれをdisるみたいなことをして、意味が違っていてもずっとその言葉を使ってしまう、
などもあるかと思います。

繰り返しになりますが、これらは全て言葉自体が同じでも指しているものが異なるもので、
どこか似ているかもしれないけど、別物として認識しておかなければならないもの、となります。
これは違う、となる境界線がどこかにあるはずで、それらを見つけることが分析のポイントになるわけです。
これが境界付けられたコンテキストです。

実装前に近い段階の話を例にすると、
EC的な通信販売を扱うサービスの開発に参加し、その中で商品という言葉があったとします。

あるビジネスチームはこの商品という言葉を構成するものとして

  • 商品名

  • 扱っている店舗
  • 値段

  • 販売期間

をあげたとします。
これをエンジニア側が一つのクラスとして表現しようとします。

次に配送などを担当しているチームと話します。
このチームはどうやら

  • 商品名
  • 
個数
  • 配送先

を商品という言葉に内包しているようです。

商品を提供している店舗に話を聞いてみましょう。
ここではどうやら商品という言葉は以下のようでした。

  • 商品名

  • 値段

これらは共通した言葉なのでそのままクラスに落とし込んだとします。

  • productName / 商品名
  • storeName / 扱っている店舗
  • price / 値段

  • salesPeriod / 販売期間
  • quantity / 個数
  • shippingAddress / 配送先

全ての要求を満たす商品クラスが完成しました!

ちょっと待って!!!

共通化されましたが、果たしてどの商品のことを指しているのでしょうか?
全てを指しているのであれば、さらに違うチームと会話をして新たな要素が追加されたら
ここにも追加されるのでしょうか?

こうなってしまうと実装レベルでも障壁が生まれてきます。
ある一方では特定の概念を表現できますが、
ある一方では不要な要素がたくさんあり、表現するためには無理して使わなければなりません。
こうなってしまうとおそらくセッターだらけであったりnullableな要素ばかりだったり・・。
所謂神クラスなどと何も変わりません。

これは境界付けられたコンテキストについて認識をせずに、
実装上都合が良いからと共通化してしまうことで発生してしまうよくある例です。
(自分も当然あります)

これらを防ぐには、同じ言葉でも少し意味が違う、というものに沿って
別なものとして表現した方が問題にたいしての表現が良くなります。

これはアプリケーションの特定箇所だけの話ではなく、
アプリケーション全体を俯瞰したときにいろんなところで見つかるものです。
利害関係者を知り、様々な体験と分析をすることでこれらを見つけて落とし込んでいくのが必要不可欠なわけです。

当然上記の例にも実は不十分な点があります。
値段が差すものは税込なのか?税抜なのか?
配送先は都道府県から?それとも市内?区内?
といった些細なものに見えて実はインパクトがあるものだったりが隠されています。

これらを見つけるのは特定の開発者だけ、というレベルの話ではないことがわかると思います。
(例外として一人で企画・運営・マネタイズ・開発などをしてるよ!というケースはありますが)

各チームのバックグラウンドに基づく重要な知識が
隠されていることが多々あるので、これらを見つけ出すのもDDDの一環です。

当然これらはみなさんのアプリケーションによって主としてみるところが変わりますので、
明確にコレさえやれば完璧!問題なし!みたいなものは存在しません。

例えばお酒が好きな人とビールが好きな人を考えても、それぞれ主にする場所が変わるわけです。
極端な話でお酒が好きな人は、特定の好きなお酒というよりも酔えれば良い!というアルコール度数を重要視するかもしれませんし、
ビールが好きな人はアルコール度数よりも、どこの国で作られたIPAなのか、ということを重要視しているかもしれません。
一般的にみると酒でも違うわけです。

こうしたことから書籍などで完璧に導き出せるものがないというのはわかると思います。
どんな名著を読んでその例をそのまま自身が携わっているアプリケーションにそのまま適用しても
(例えばEメールについて本にこう書かれていたのでこうだ!みたいな)
何にもならず、自分たちの注力外の概念が無理矢理結合されるだけとなります。
実装パターンだけ追っても複雑さの解決はされないのです。

エンティティ、モデル

分析の話ばかりで飽きたところに少しだけ開発に関する知識の話をしましょう。
DDDについて少し理解したり、書籍を読んだりすると出てくる言葉にエンティティとモデルという言葉があります。

実はこれは大きくミスリードしてしまう要素でもあり、
開発における境界付けられたコンテキストの代表みたいなものです。

タイトルにもあるLaravelだと、例えばデータベースアクセスを表現する「モデル」と呼ばれるものがあります。
実はこのモデルという言葉はさまざまな意味があります。

ドメインモデル、データモデル、Eloquent
モデル(他にもありますよ!)などをモデルと指すことが多いですが、
どれも違うものを指しています。

どちらか片方の知識だけで片付けてしまうのは、これまでの文章にあったように
実は大変危険で複雑化してしまう原因になります。
それぞれのモデルを正しく理解して一緒に考えるのは止めるのをお勧めします。

例えばDoctrineやtsのtypeORMでもありますが、エンティティ。

DDDにおけるエンティティとは、

ドメインにおける識別しなければいけない特別な存在のことでで、
データモデルにおけるエンティティ
とは、
情報収集する対象のこと
、データベースの行に近いものではありますがそれ自体ではありません。

まったく同じ言葉ですが全くの別物なわけです。
言葉が同じということでエンティティをひとまとめにしてしまうと、
簡単にデータベースなどの入出力と結合してしまったものになってしまいます。

同時にモデルという言葉が同じであっても全く意味が異なっています。
どちらかの知識に寄せて片付けてしまうのは大きな誤解を産んでしまいますので注意が必要です。

これまで述べてきた内容にもありますが、
データベースなどの知識や考えを排除して考えましょう。
データの入出力やAPIのコールなど現実的な事柄がいくつもあるのは当然なんですが、
これらについては後で考えましょう。
むしろ実装でも一番最後くらいでちょうどいいです。

とはいえ実装に落とし込む方法を知りたいんだ・・

この話をしてしまうとそこにフォーカスしてしまうので、
複雑なものにたいする具体的な実装コードサンプルを載せるということはしませんが、
ヒントのような形で雑に載せておきます。

Laravelの機能が楽なので使いたいんですがどうしたら?

使いましょう。
ただDDD入門についてこれまで書いたようにきちんと分析し、
その分析した世界に極力それ以外の知識、つまり非機能要件を持ち込まない工夫が必要です。
とはいえ高度な抽象化やかっこいいパターンを無理に真似する必要はありません。
便利な機能を使ったメソッドなどをインターフェースに含めるなどするだけで問題ないです。

Eloquent使ってもいいの?

どうぞ!
他上記と同じ

リポジトリパターンが巨大で・・

データベースのテーブルなどと対になった作りになっていませんか?
抽象レイヤではなく、まずは特別視しなければならない存在の塊を操作するもの、
そしてこの特別な存在が微妙に違うのであればそれを操作するリポジトリも分けてみましょう。
仕様パターンなどの解決方法がありますが、難しければ無理に取り入れなくても良いでしょう。
まずは少しでも中身がちがう存在を操作するものが混ざっているのであれば分けましょう。

DDDをもとに導かれたコードを
引き継いだがわからない

分析したチームなどが存在しないのであれば、再度分析をしてください。
今のチームの認識とそのときのチームの認識が異なっていて当たり前です。

分析した結果、今の概念と異なるのであれば
新しい知識に基づいて実装し直すなど適度なリファクタリングを重ねましょう。

全てでDDDで題材にされるパターンを用いなければなりませんか?

他のシステムですでにドメインが表現されているのであれば必要ありません。
特別なロジックもなければ、取得して適当に成形するだけで十分、
といった場合はDAO、DTOなど用いましょう。

繰り返しになりますが、なにがビジネスロジックでみたいな判断基準は
みなさんの分析結果や解決しないといけないものは何か、
などの知識基準の話でこういうのは実装しなくていい、みたいな答えはありません。
かならずチームなどで導き出した共通認識の中で判断してください。

最後に

いくつかポイントなどを書きましたが、
この内容のうちどれかは必ずやっていることだと思います。
例えば開発チームとビジネスチームで話を聞く、みたいなことも良くあると思いますし、
アジャイルなどを採用しているところはまさにそういう毎日だと思いますし、
社内ツールを作っている方などはカスタマーサポートチームと話をするということは
日常的なことでしょう。
これらは全て自分たちのアプリケーションをどうやって要件に合わせていくか、
というところがベースになっているはずです。

つまりDDDという名前がついていますが、
ある程度のことは意識せずに大体みなさんやっているわけです。
それをどこまで認識合わせして落とし込んでいくか、というところを主として開発を進めているわけです。
分析の精度を上げるための手法だったり、会話の認識合わせ方法だったりはいくつかありますので、
チームだったり個人に合わせて導入するといいでしょう。
この辺りは非常に有益な本がたくさんありますので是非読んでください。

会話だったり、多少の抽象的なものの考え方だったりに慣れやセンス的なものが必要だったりはします。
この辺りは開発から少し遠いと感じるかもしれませんが、ロジカルシンキングなどの本を読むといいでしょう。

冒頭の例に挙げたいくつかの質問は全くの違うレイヤの話で
DDDの話ではないということが少しでもわかっていただければ幸いです。
(冒頭の質問に対する答えはネット上に素晴らしい記事がたくさんありますのでそちらを参考に)

  • 実装方法起点ではなくビジネス、利害関係者を起点に表現する
  • データの入出力を考えるのは一番最後、分析時などには考えない
  • 流行りモノではなく当たり前なことをやるだけ

こうしたことを意識しておけば入門して実践することがおそらくできるはずです。
(実装方法は別の話ですよ)

DDDに限らず、自身のチーム合わせた開発スタイルを取り入れて問題解決に取り組んでいきましょう。

実践ドメイン駆動設計

実践ドメイン駆動設計

エリック・エヴァンスのドメイン駆動設計

エリック・エヴァンスのドメイン駆動設計

  • 作者:Eric Evans
  • 発売日: 2013/11/20
  • メディア: Kindle版