本記事では、トランザクションスクリプトの資産を活用しつつ、DDD + Clean Architectureなシステムを新たに開発していく試みを紹介します。
現状まだ開発途中、大きな成果も目に見えていない段階ですが、似たような境遇の方の参考になれば幸いです。
データオブジェクト中心の従来システム
対象となるのはとある業務アプリケーションのバックエンドで、リレーショナルデータベース(RDB)を中心に多くのコンポーネントが動いております。
コンポーネントは非同期メッセージング、日次バッチ、UI用のWebAPIなど、数十種類が動いているイメージです。
各々コンポーネントで入出力・ロジック部分は別れておりますが、データアクセスの実装は共通で持っている状態です。
基本的にRDBテーブルの1行をgetter/setterのみを持つデータオブジェクトにマッピングし、それをこねくり回して結果を保存・返却するという作りです。いわゆるトランザクションスクリプトパターンとなっています。
また各コンポーネントのアーキテクチャは、下図のようなレイヤードな構成となってます。
従来システムのつらいところ
このシステムを改修し続けて、様々な問題点が見えてきました。
- Service層で扱うデータは、RDBをそのまま取り出したデータオブジェクト。関心事と外れたフィールドをデータオブジェクトが持っていたり、一度にたくさんのデータオブジェクトを集める必要が出てきたりする。
- Service層におけるクラス設計にルールがなく、データオブジェクトをどこでどう変更しているか掴みにくい。結局どんな結果が返ってくるかは、入り組んだクラスの中を最初から順に処理を追っていくしかない。
- コードに触れないマネージャー(いわゆるドメインエキスパート)と要件を詰めるとき、現行仕様がどうなっているのか、処理を一通り追わないと答えられない。またその改修の際、システム実装への翻訳作業が入り二度手間、かつ要件との誤差が生まれる。
Evans本で触れられている、いわゆる「ドメインモデル貧血症」に陥っている状態です。
これらの問題を解決する術はないのか?解決策として注目したのがDDD、Clean Architectureでした。
DDD + Clean Architectureを適用
既存のものをすべてリファクタリングするのも困難なので、まずは新規開発することが決まったコンポーネントについて、アーキテクチャを考え直してみることにしました。
新規コンポーネントの制約は次の通りです。
- 非同期メッセージング処理を行う。Message Queueからリクエストを受け取り、処理を行ったあとMessage Queueで次のコンポーネントに次のリクエストを投げる。
- データソースは今までと同じRDB。テーブルへのアクセスはData Access層の実装を使いまわせそう。
- ミニマムスタートな案件で開発するため、頻繁に改修が加えられる予定。
これらの制約から考えたアーキテクチャの全体像がこちらになります。
Clean Architectureに沿いつつ、既存のData Access層を取り入れた構成としました。
その特徴を解説します。
入力→処理→出力はCleanに一方通行に
Controllerで入力を受け取り、Applicationに書かれた順序で処理して、Presenterで出力という、Clean Architectureそのままの形にしました。
ここで重要なのが、Applicationにはロジックを置かず、あくまで処理順のみ記載という点です。これを意識して設計することで、入り組んだクラスを追うことなく、どんな処理を行っているか全貌が理解しやすくなります。
実際のクラスの関係は下図の通りです。
InputUsecase, OutputUsecaseというインタフェースを置くことで、入出力の実装に依存しない形になります。これにより、Message Queueの向き先などが変更になった際も柔軟に対応可能になります。
今回は入力・出力がいずれもMessage Queueであるため、出力部分をPresenterとしてそのまま採用できました。
一方で、Spring MVCなどを利用したWebAPIの場合はその仕様から難しいと考えられます。(レスポンスをControllerの戻り値として返す必要があるため)
そういったケースでは、Presenterを消してControllerで返却する形にすると良いそうです。1
ドメインモデルを作成し、そのオブジェクトをDomainに配置
Applicationに処理の順序のみを意識するのに対し、Domainではビジネスロジックのみを反映させるようにします。
ドメインモデルを反映したクラス、ドメインオブジェクトを配置します。
ドメインモデルについては、ドメインエキスパートと一緒に考えて組み立てます。
PlantUMLでオブジェクト図を作成しながら行うとやりやすかったです。下図はその例です。
(『実践ドメイン駆動設計』 P.356, 図10-7をもとに作成。スクラムのプロジェクト管理を行うためのツールで必要な、バックログアイテムのドメインモデルを考察しているもの。)
ドメインモデルと、その実装のドメインオブジェクトについてはテクニックが必要です。本記事では省略しますが、他のDDD関連の記事や書籍などを読み込むとイメージつかめるかと思います。
ドメインオブジェクトとデータオブジェクトの差異はGatewayにて吸収
ドメインオブジェクトはビジネスロジック、「やりたいこと」に特化したオブジェクトであるため、DB都合のデータオブジェクトとは相容れません。
その違いを吸収するため、Gatewayでデータオブジェクトとドメインオブジェクトの変換を行う形にしました。
クラスの関係は下図の通りです。
Data Accessの部分は従来システムのものを流用しており、それをGatewayにて変換します。変換処理まるごとRepositoryImplに置くのも複雑になるので、マッピングするためだけのクラスをConverterと別で定義してみました。
Gatewayにて変換を行うおかげで、既存のデータアクセス部分の実装を引き継ぎつつ、ドメインモデルを利用した設計を構築することができました。
また、将来的にRDB自体が置き換わることになっても、Gateway以下を修正するだけでロジックに影響はないようにすることが可能です。
まとめ
DDD + Clean Architectureを利用して、従来の資産を活かしつつ保守性の高い設計ができたと思います。
まだ取り組み途中ですが、所感は以下の通りです。
- 既存のRDBアクセスの実装を流用し工数減らしつつCleanにできた。
- クラス設計のルールを決めたことで、どこに何を書けばよいか迷わなくなった。
- ドメインエキスパートと認識齟齬を少なく改修し続けることができそう。
次はもう少し実装にフォーカスした記事を書きたいと思います。
-
nrsさんのJava実装の記事が参考になります: https://nrslib.com/clean-architecture-with-java/ ↩