DDDにおいて、外部連携先のコールバック処理は鬼門となるので注意したい話
DDDを実践するにあたり、外部SaaS等のデータ構造がドメイン層まで食い込んでいるのをよく見かける。
これは複雑な連携であればあるほど避けるべきなのに、複雑な連携こそ、そうなっている事例をよく見る。
どうしてか考えてみたところ、それらには共通して「コールバック」による状態通知のような処理が存在していることに気がついた。(それ以外の場合は普通にアーキテクチャやモデリングに問題がありそう)
これを「自組織のフロントエンドやアプリとのやりとり」と「コールバック通知のやりとり」で分けて考えると問題点に気づきやすいのではと思ったので書いてみる。
自組織のフロントエンドやアプリとのやりとり
自組織だったり、公開APIとしてのやりとりで使うrequestやresponseは、割とドメインモデルに近い形をしている。 ドメインモデルに近い形では厳しい場合にCQRS+ESにしたり、一部そのままでは都合の悪い場所をプレゼンテーション層で加工したりする程度である。
PrimaryAdapter -> requestToDomainModel -> UseCaseArgs(has a domainModel) -> UseCase
という流れで、ここに問題はない。(domainModel変換なしでUseCaseArgsに詰めてUseCaseでRepositoryを引くパターンももちろんある)
コールバック通知のやりとり
一方、この方法で外部連携先のコールバック通知を実装するとどうだろう
PrimaryAdapterが受け取ったものをそのままrequestToDomainModelとしてしまうが、コールバックのリクエストの形は外部連携先が管理しているただのデータ構造だ。
これが無遠慮にドメイン層に流れ込んでくることになる。これを許容して良いのだろうか。
これでは外部連携先のデータ構造がドメインに漏れ出てしまい、変更容易性も凝集性も結合度もすべておじゃんである。
ではどうすればいいのだろうか
この外部のデータ構造そのものはUseCase層に置き、ただのDTOとして扱うべきだと思う。 これはUseCaseの引数以上の意味はなく、我々のドメインには全く関係がない。
流れとしては👇
PrimaryAdapter -> requestToDto -> UseCaseArgs(has a DTO) -> UseCase
※もちろん、内部のパラメーターの一部がドメイン足りうることはある。外部連携用IDとか。だが、構造そのものがドメインになることはほぼない。
こうすればドメインは真にやりたいことに注力でき、構造も外部連携先に左右されず、処理も変に外部連携先のデータ構造と密結合になることもない。(変換層があるとはいえ、同じ形に変換していたら疎結合とは言えないのではないだろうか)
本来、requestToDomainModel
とするのも手間を嫌うのと実用上問題ないからであって、理想論はすべてDTOを噛ませるべきなのだろう。(手間がでかすぎるしメリットがないのでやらないけど)
保存はどうするのか
おそらくここが意見の分かれるところだと思われる。 外部連携先からの情報はもれなく保存していたほうが無難である。だが、ドメインとしては扱いたくない。
自分は👇で書いたDelegateProcess相当の処理でDTOをデータモデルに変換して保存している。 再考 - ドメインサービス - まっちゅーのチラ裏
こうすれば、ドメインはいつでも情報の過不足なくコールバック通知の情報を元にモデルを組み立てられるし、構造はドメイン層に漏れ出さない。
他には、外部連携用のコンテキストを分けてしまい、そこではガッツリドメインとして登場させる等の手法も考えられるがそこまでやるのはオーバーキルかなと思っている。
考え方
UseCaseに何もやらない作業者を想定しない
という考え方が良いのかなと思う。
ECサイト作る際、商品配送を外部の配送会社に委譲することを考える。
配送会社ごとに伝票のフォーマットが違うと仮定したとき、ドメイン層に外部連携先のデータ構造を置くということは
- ①作業者 -> ユーザーの配送情報をください!
- ②ドメイン層 -> ホイ。配送情報です。
- ③作業者 -> azs! 伝票書きたいんだけど書き方がわからん!配送会社Aの伝票の左上の欄には何を書くべきですかね?
- ④ドメイン層 -> 住所やな。というかハイ。書いといたわ。伝票。
- ⑤作業者 -> わーい。配送会社に出しておきますね!
という流れである。めちゃくちゃドメイン層に頼りまくってる。
データ構造を置かない場合の流れは👇
- ①作業者 -> ユーザーの配送情報をください!
- ②ドメイン層 -> ホイ。配送情報です。
- ③作業者 -> azs!伝票書いちゃお〜〜
- ④作業者 -> 伝票できた!配送会社に出したろ!
むちゃくちゃスムーズで、いちいちドメインが伝票の構造を把握する必要もない。
もちろん、ドメインが伝票相当の構造に興味があるケースもあるかもしれないが、ほとんどのケースでは興味がないはずで、伝票のどこに何を書くかくらいの情報は、知識というよりは作業者がアドホックに判断するものとした扱ったほうがスマートでないだろうか。
表示したいという1点だけで興味があるケースもあるが、それは伝票IDとTypeをpresentation層に持っていって直接データ引いちゃったり、CQRS+ESにしたり、UseCaseで表示用のDTOを持っちゃってPresenterでResponse用のモデルに変換したりで回避できる。
まとめ
コールバック通知等の情報を同期する系の連携は密結合になりやすいという話でした。
外部連携先オブジェクトのライフサイクルそのものにめちゃくちゃ関心があるのであればドメインに顔を出さざるを得ないと思いますが、構造そのものを出す必要があるのかはめちゃくちゃ考えてできるだけ避けていきたいですね。
自分が大規模システムで組むアーキテクチャは、下から上の依存は構造として参照すらできないように制限してるのですが、下へ下への圧力が強くなりがちなのかもと思うところがあったので、上の層における部分は置くようにし、下から上への圧力も意識してドメインをシンプルにし、本来やりたいことに注力できるようにしていきたいと思います。