ログ日記

作業ログと日記とメモ

PHPでDDDのようなClean Architectureのような単純なLayeredとIoC

最近PHPコードをリファクタリングしている。

DDDやクリーンアーキテクチャの記事や
5年間 Laravel を使って辿り着いた,全然頑張らない「なんちゃってクリーンアーキテクチャ」という落としどころ
この辺の記事を読んだりして、自分なりの最適解を探している。


まだやり始めたところだけど、現状をメモしておく。


まず、PHPで今書いているコードは基本的にUIに関連する処理が最も多く、ドメインロジック的なものよりプレゼンテーションロジックが多いということをふまえて。

src/
└── Ddd
    ├── Application
    │   ├── UseCase
    │   │   └── Cart
    │   └── ViewUseCase
    │       ├── Cart
    │       └── Product
    ├── Domain
    │   ├── Cart
    │   └── Product
    └── Io
        └── Infrastructure
            ├── Cart
            ├── Order
            └── Product

アプリケーション層のユースケースと同じ階層に表示のためのユースケースを入れる。
これは、表示のための文字列を整形する処理が結構な分量あり、それをテストしたいため。CQRS(というかCQS)のクエリ結果を整形して、それが正しく整形されたかチェックしたい。なので複雑なクエリもアプリケーション層に入れる。
ViewUseCaseではデータの保存は無し。
「保存 => 結果を取得して表示」の場合はまずUseCaseで保存して ViewUseCase のクエリで表示用のデータを取得する。

また、リポジトリはアプリケーション層に所属する。ドメインリポジトリを扱わない。
他のDDDなどの解説で、ドメインロジックがfindやstoreを実行しているけど、それってユースケースでは?という単純な疑問がある。あとユースケースに沿ったSQLを書きたくて、ドメイン用の全部入りSQLは発行したくないので。
それにstrict layeredとの相性も悪そうだった。
ドメインロジックは、ロジックというか型変換に徹する。DomainではなくてType というネームスペースにしようか迷ったぐらい。基本的にはユースケースのコードが膨らむことになる。
そこは諦めてテストを書く。
そうすると結局、昔ながらのレイヤー構造+IoCリポジトリだけ依存関係を逆転させた普通の構造になったかもしれない。



テストはユースケースに対して行う。ドメイン(というかここでは細かい型用のクラス)はリファクタリングしたいのでテストを細かく書かない。
ユースケースにプレゼンテーションを含めたことにより、ブラウザが表示するUIと直結しているので、UIのテストも多少は兼ねることになっている。


PHPのファイルも込みで並べると

└── src
    ├── Controller
    │   ├── Cart
    │   │   └── IndexPage.php
    │   └── Product
    │       ├── DetailPage.php
    │       └── IndexPage.php
    └── Ddd
        ├── Application
        │   ├── UseCase
        │   │   └── Cart
        │   │       ├── AddCartUseCase.php
        │   │       ├── CartItemRow.php
        │   │       ├── CartRepository.php
        │   │       └── CartRow.php
        │   └── ViewUseCase
        │       ├── Cart
        │       │   ├── CartDto.php
        │       │   ├── CartItemDto.php
        │       │   ├── CartQuery.php
        │       │   ├── GetCartPageUseCase.php
        │       │   └── RowDtoConverter.php
        │       └── Product
        ├── Domain
        │   ├── Cart
        │   │   ├── Cart.php
        │   │   └── CartItem.php
        │   └── Product
        └── Io
            └── Infrastructure
                ├── Cart
                │   ├── CartQueryImpl.php
                │   └── CartRepositoryImpl.php
                ├── Order
                └── Product

こんな感じになっている。
UseCaseからViewUseCaseへの参照は無し。逆はあり。

Row はリポジトリからのマッピングクラス、Dto はUIへのマッピングクラスになっている。
本当はここもインターフェースと実装クラスを分ける方が綺麗だけど、大量にあるカラムのgetterを用意するのが面倒なので、オブジェクトのpublicプロパティにした。そうするとユースケースがDBのカラムを少し知っていることになるけれど、これは妥協で。インフラ層で詰め替えてもいいけど、インフラは基本的にコードを少なくしたくて、何の加工もせずにそのまま返したい。加工はアプリケーション層でやって、それはテスト対象になる。本気で切り離してもあまりメリットはなく、変更を追いやすく修正しやすければ良いので。

基本的にstrict layered、つまり階層を飛び越えて参照しない。
コントローラーからはドメインを参照しない。ここを甘くするとテンプレートでメソッドを呼び出したりしてしまうので。
テンプレートエンジンに渡すのは、ユースケースから返ってきたDtoをそのまま渡す。
コントローラーでフォームの処理とエラーチェックをしているけど、本当は Io\Form 等に入れたい。まだそこまで行ってない。

とりあえず今はこんな感じ。