こんにちは、福岡でエンジニアをやってます、んだです。
トランザクションスクリプトパターンが気になったわけ
まず、トランザクションスクリプトパターンがなぜ気になったのかについてですが、案件でユースケース記述を元に実装してみたことがきっかけです。
ユースケース記述では、ユースケースが達成するためのシナリオを書いていきます。そして、ユースケース記述を元にロバストネス図を作成していきます。
実装段階では、ロバストネス図を参照しながら、手続きの流れに沿って業務手順をそのままコードに置き換えるイメージで実装しました。
言葉ではわかりづらいかと思うので、サンプルコードを↓で書いてます。
nda-desu.hatenablog.com
実装自体はロバストネス図で実装のイメージができていたので割とスムーズにできたのですが、時間を置いてふと考えたときに
「ユースケース記述の通りに手続き的に実装するのは...問題ないんだろうか?」
と疑問を持ちました。手続き型はよくないという記事をどこかで読んだ気もするし、変更容易性とかどうなんだろ?と。
この疑問を解決するべく調べていたところ、トランザクションスクリプトパターンに突き当たり、原書を読んでみました。
トランザクションスクリプトパターンについて
というわけで、まずはトランザクションスクリプトパターンの概要から書いていきます。
トランザクションスクリプトパターンの出自
トランザクションスクリプトパターンは、Patterns of Enterprise Application Architectureの本の中で紹介されているドメインロジックを表現するためのパターンの一つです。
ちなみに、ドメインロジックを表現するためのパターンは、
① トランザクションスクリプト
② ドメインモデル
③ テーブルモジュール
と、3つのパターンが紹介されています。
トランザクションスクリプトとは
トランザクションスクリプトとは、「業務手順のコード化」です。*1
実装時には、処理を順々に記述していくことになります。
書籍の方でも説明を見ていきましょう。
A Transaction Script is essentially a procedure that takes the input from the presentation, processes it with validations and calculations, stores data in the database, and invokes any operations from other systems.
トランザクションスクリプトでドメインロジックを表現する場合、基本的には
① プレゼンテーション層からInputを受け取り
② Inputされた値を検証したり計算し
③ データベースに保存し
④ 時には他のシステムを呼び出す
という一連の手続きによってドメインロジックを表現します。
トランザクションスクリプトパターンを具体的に考えてみる
「論よりコード」ということで、僕の理解を元にトランザクションスクリプトパターンを具体例を元にコードで表現してみます
具体例として、ここでは
「社員情報管理システムで、社員情報をcsvダウンロードする」
で考えます。
まずはコードで表現する前に「社員情報管理システムで、社員情報をcsvダウンロードする」のユースケース記述の主成功シナリオをざっと書いてみます。
主成功シナリオ
① 社員情報をDBから取得する
② 取得した社員情報をcsvデータに変換する
③ csvファイルとして出力する
シナリオとしてはこんな感じでしょう。
では、このシナリオをトランザクションスクリプトパターンの考え、すなわち「業務手順のコード化」に従ってクラスを作ってみたいと思います。
ディレクトリ構成
具体的に考えてみたディレクトリの構成とクラスです。 UseCasesとServicesとRepositoriesの3つの層に分けてます。
App ├ UseCases/ ├ DownLoadEmployeeData/ DownLoadEmployeeDataUseCase.php // 社員情報をcsvダウンロードする ├ Services/ ├ DownLoadEmployeeData/ CsvDataService.php // ↓の2つのServiceを使って変換&ダウンロードする ├ CsvData/ ConvertCsvDataService.php // 社員情報をCSVデータに変換生成する OutPutCsvDataService.php // CSVデータを出力(ダウンロード)する ├ Repositories/ ├ DownLoadEmployeeData/ GetEmployeeDataRepository.php // 社員情報を取得する
UseCaseでは、Repositoriesから社員情報を取得して CsvDataServiceに渡します
class DownLoadEmployeeDataUseCase { public function __construct( private GetEmployeeDataRepository $getEmployeeDataRepository, private CsvDataServiceInterface $csvDataService, ) { } public function download(): bool { try { $employeeData = $this->getEmployeeDataRepository->findAll(); return $this->csvDataService->run($employeeData); } catch (Throwable $e) { throw new Exception($e->getMessage()); } } }
CsvDataService では、Repositoriesから取得した社員情報をCsvDataに変換してダウンロードします。
class CsvDataService { public function __construct( private ConvertCsvDataServiceInterface $convertCsvDataService, private OutPutCsvDataServiceInterface $outPutCsvDataService, ) { } public function run(array $employeeData): bool { $csvData = $this->convertCsvDataService->convert($employeeData); return $this->outPutCsvDataService->output($csvData); } }
① プレゼンテーション層からInputを受け取り
② Inputされた値を検証したり計算し
③ データベースに保存し
④ 時には他のシステムを呼び出す
が、PofEAAで紹介されているトランザクションスクリプトでしたが、上のコードの例でも、手続きをほぼそのまんまクラスに置き換えて、UseCasesの中で手続きの順番にしたがって呼び出してみました。
トランザクションスクリプトの利点と使いどころ
では、トランザクションスクリプトの利点と使いどころについてPofEAAの書籍を元に書いていきます
トランザクションスクリプトの利点
PofEAAでは、トランザクションスクリプトの利点として「シンプルさ」「開発者の理解のしやすさ」などをあげています。
たしかに、ユースケース記述の流れをそのまんまコードで表現すればよいので、シンプルかつ実装はしやすいように感じます。
トランザクションスクリプトの使いどころ
トランザクションスクリプト使いどころとしては「ロジックが単純であること」をあげています。逆に、ドメインロジックが入り組んで複雑である場合には、不向きだと書かれています
「社員情報管理システムで、社員情報をcsvダウンロードする」の例を再考してみましょう。
このユースケースの流れは、
社員情報を取得してきて→csvにして→出力する
と、ロジック自体に複雑な条件はなく、かなりシンプルでした。
そのため、手続きをそのまんまコードに落とし込んでも入り組んだ実装はなく、シンプルにクラスを分けることが出来ました。
では、複雑なドメインロジックに対してはどうでしょうか。
条件が複数あったり、法律が絡んでいたり、外部システムとのやりとりの中があったりすると...
んーーーー、トランザクションスクリプトは業務手順をコード化するので、ドメインロジック自体が複雑である場合は、そのまんま表現してしまうとコードも複雑化しそうです、
そうなると、まず実装もしんどそうですし、保守性、変更容易性の観点から考えても、あかんでしょう、
まとめ
「ユースケース記述の通りに手続き的に実装するのは...問題ないんだろうか?」いう疑問から調べてみたトランザクションスクリプトパターンでしたが、
あたくしの結論は
業務手順がかなりシンプルである場合は、トランザクションスクリプトパターンでもよい
ドメインロジックが複雑である場合は、トランザクションスクリプトパターンは採用しない
です。
ロジックがそれほど複雑ではないプロジェクトに関しては、ユースケース記述からロバストネス図を作成して、業務手順をコード化する方が、スムーズに実装できるんではと思います。
逆に、条件が複雑であったり、外部システムとの連携がいろいろあったりするようなシステムにおいては、業務手順は複雑化しているはずなので、このパターンで実装するとコードも複雑化しそうです。
ただ、PofEAAでも語られていますが、ドメインロジックが複雑であるかどうかは測定する術はないため、判断基準は迷うところです。ここの判断は経験則になりそうです。
最後に...ドメインロジックが複雑である場合ですが、その場合は「ドメインモデルパターン」が有効であるらしいです。
ということで、次は ドメインモデルパターンについて調べていきたいと思います。
いやまいったね、僕から以上。
参考