今回業務で初めてドメイン駆動設計(以後DDD)をiOSアプリへ導入してCleanArchitectureを目指してみたのでまとめとその所感をつらつらと書いてみます。
今回はたまたまiOSで導入したのでiOS前提で話を進めますがAndroidにも適用できる汎用的な概念です。
上辺だけの知識+オレオレルールで導入を始めたので間違っていたらご指摘お願いしますm(_ _)m
そもそもドメイン駆動設計って?
MVCなどの概念と同じ(ちょっと違うけど)設計アーキテクチャの一つで、iOSやAndroidアプリ開発でおこりがちな、ファットコントローラーを解決することができるかもしれない設計手法の一つです。
MVCじゃだめなん?
MVCを採用するiOSやAndroidアプリでは、コントローラクラス(UIViewControllerやActivity)の責務が非常に広く、クラスが肥大化しやすいという問題点があります
また画面単位でコントローラが生成されるため、画面ごとにロジックを書いてしまいがちです。
DDDではそれを止め、業務的関心事(ドメイン)を中心としたクラス設計を行います。
DDDではコントローラクラスはすべてViewと見なされ、UI操作のみに特化させます。
以下がDDDの概念図になります。
円の外側が外界(アプリケーションの外)で場合によってはUIだったりDBだったりクラウドだったりします。
外から中へはアクセスできるが中から外へはアクセスできない(知らなくていい)作りにします。
概念図の外周の青い部分では主に外界とのコミュニケーション機能を提供します。
外界の相手が人間だったらUIを変更したり、相手がDBだったらデータベースへのアクセス手段を提供し、API相手ならAPI通信を担うコードを実装します。
緑色の部分ではビジネスロジックとの仲介役を担います。
この部分が各種イベント(ボタンが押された等)のハンドリングを担います
赤の部分はUseCaseと呼ばれビジネスロジックを記述します。ここではどんなアプリケーションで動いているか、動いているのがiOSなのかAndroidなのか、データを取ってくるの先がデータベースなのかAPIなのかといった円の外側の事象は一切気にせず、業務関心事のみにフォーカスしてロジックのみを記述します。
中央の黄色い部分はモデルクラスです。ここには処理は記述せずUseCaseで使う入れ物(モデル)だけになります。JavaだとJavaBeanとかPOJOとか呼ばれたりしますね。
なんとなく概念はわかったけどiOSアプリに導入するにはどうしたらいいねん
まずはクラス設計をしてみます。この辺の記事を大いに参考にさせていただき、大きく以下の3つのレイヤーにわけました。
大きく3層に分けます。iOS開発的には最上位のフォルダ分けがこの3つになりますね
Presentation層とData層は概念図における外側になり、Domain層が円の内部になります
上層から下層へ向かって処理が流れ、Presentation層からData層を呼び出したり、処理を逆流したりはしません。
ちなみにこれを上の円の概念図に当てはめると以下のようになります
ここでは仮にAPI通信と内部DBへアクセスする機能があると仮定します。
Presentation層
ここでは概念図における外側になり、MVPの概念も取り入れています。
UIの制御を行うViewとイベントのハンドリングを行うPresenterに分けます
1画面につき対応するViewController + Presenter(+ コールバックのためのDelegate)が基本でViewControllerで受け取ったイベントは基本的に全てPresenterへ流します。
その後場合によってはさらにDomain層のUseCaseへ処理を依頼したり、UIの更新を行うためにDelegateを通してViewControllerへ指示を出したりします。
※上記MVPモデルのModel部分がDDDにおけるDomain層、Data層のレイヤーになります
非同期処理を実装する場合はPresentation層→Domain層へ処理を依頼するタイミングで非同期へ移行し、その他のタイミングの非同期化は行いません。
後述しますが非同期化のための手段としてはRxSwift等のReactiveプログラミングで実現するとスマートだと思います
Domain層
ここは概念図における円の内側になります。
コアなビジネスロジックのみを担当し、iOSフレームワークには依存しない作りにします
iOSに依存しないのでUIKitなどは登場せず、純粋なSwiftのみで記述します
またPresentation層やData層も意識せず、画面単位の考え方もしません
(極端な言い方をすればPresentation層とData層をアプリからごっそり取り除いてもDomain層でエラーが発生しない)
Data層
この部分はまた概念図における外側になります。
主にUIに絡まない外界とのやり取りを担当します。大体はデータの入出力になりますね
どこからデータを取ってくるかなどはこの層に収めます。
依存関係を図にしてみるよ
上記の3つのレイヤーの依存関係は以下の通りです
後述しますがレイヤーを跨ぐ場合、上位レイヤーはDIライブラリを用いて下位レイヤーのインターフェース(Protocol)のみを知っている状態にします。
処理の流れを図にしてみるよ
処理の流れは先のレイヤーの上から下へ流れる形になります。
上層は下層のインタフェースをクラスのメンバー変数に保持しておきます。
その際に注意する点としてはPresentation層の中で循環したりDomainからDomainを呼び出したりしないようにします
概念図上の円の外側から入ってまっすぐ内側まで到達したらそのまま外側に出ていくイメージですね
この流れを実現する上でひとつ問題になることはDomain層とData層のやりとりにおいて処理の流れと依存関係が逆だということです。
依存されている方から依存している側を呼び出すことはできないので矛盾が生じていますが、これを解決する手段としてDependency Injectionが存在します。
SwiftだとSwinjectやTyphoonといったDIライブラリが存在するため、そういったライブラリを用いて外部から依存性を注入することで依存関係が逆転しても処理を投げることができます。Android(Java)ならDagger2が鉄板ですね。
DIライブラリをレイヤー間を跨ぐ場合に用いれば、インタフェース(Protocol)だけを知っている状態になるので、レイヤー同士の結合度を下げることが出来るため独立性、保守性が上がります。
仮に、ユーザーがデータ取得ボタンを押すことでAPIからデータを取得し、ローカルDBへ保存してその結果を画面に表示するという機能があった場合、流れは以下のようになります。
Reactiveプログラミングをレイヤー間で使う
GUIアプリを開発する上でついて回るのが非同期処理です。
Domain層ではiOSフレームワークを意識しないのでもちろん今自分が行っている処理が同期処理なのか非同期処理なのかも気にしません。
そこで登場するのがReactiveプログラミングです。
RxSwift等のReactive系ライブラリを用いてドメインからの戻り値をすべてObservableにすることで処理をどう扱うかをPresenterに任せることが出来ます。
ReactiveプログラミングについてはDDDを実践する上で必須ではありませんが、使うとより(・∀・)イイ!!感じになると思います。
実際のクラス構成にしてみた所
実際のXcode上のフォルダ構成は以下のようになりました。
正直、各レイヤー配下のフォルダ構成は必要に応じて追加していったので、明確なルールはなくプロダクトによって大きく変化すると思います。
私の開発アプリではこうなりましたという一例と捉えてもらえると幸いです
また、この配下にもアプリ次第で多くのフォルダ構成が連なると思います。(この画像に見えているフォルダの配下にも実際たくさんフォルダはあります)
(AppDelegate.swiftは厳密にはPresentation層にいれるべきかも)
あとは実装あるのみ
あとは実装あるのみなのですが、そもそもここまではDDDをiOSに導入するための枠組みでしか無いので、ドメイン駆動設計の思想に従って実装を進めます(雑)
「どうなったか編」へ続きます。