こんにちは、農業系プロダクトの開発を担当している糸井です。
弊社ではピンポイントタイム散布(以下、PTS)という防除のデジタル化サービスを展開しております。
PTSシステムのバックエンドではSpringBootを使用しております。
立ち上げ期より、既存の農業系プロダクトの課題感をもとに改善を進めてきましたので、その軌跡をご紹介します。
1. OnionArchitecture
PTS以前の農業系プロダクト
Package構成
(実際はMultiModuleで構成されています
app
└── src
├── main
│ ├── java/...
│ │ ├── controller
│ │ │ └── XxxYyyController.java
│ │ ├── dao
│ │ │ └── XxxYyyDao.java
│ │ ├── dto
│ │ │ ├── XxxYyyDto.java
│ │ │ ├── XxxYyyRequest.java
│ │ │ └── XxxYyyResponse.java
│ │ ├── entity
│ │ │ └── XxxYyy.java
│ │ └── service
│ │ └── XxxYyyService.java
│ └── resources/...
│ └── XxxYyyDao.xml
└── test
├── java/...
│ ├── controller
│ ├── dao
│ ├── dto
│ ├── entity
│ └── service
└── resources/...
└── XxxYyy.csv
課題感
(先人が整理してくださった課題感の抜粋です
(内部品質の投資について
の図は、 Is High Quality Software Worth the Cost? を独自に翻訳しております。
上記スライドの他にも、以下のような記述が残されていました。
## 既存構成の問題点 1. 責務があいまい - APIの外部仕様が変わった時に`@Service`のアノテーションがつくメソッドまで書き換えないといけない - APIの返り値の型が`ApiResponseList`から`ApiResponseObject`に変わっただけなのに、なぜ`@Service`のアノテーションがついたクラスまで書き換えないといけないのか?(SOLIDの何かの原則に違反しているような? 1. `@Service`のアノテーションが着くクラスに全ての処理が書かれており、変更がしにくい - Fat Service になっている <=> (いわゆる)ドメイン貧血症 - 一覧取得と追加と削除は別機能では?(SOLIDの何かの原則に違反しているような?) - 他の場所からもタスク関連の処理を使う時にタスクの仕様を`@Service`のアノテーションがついたクラスに書くと、仕様を見落とす可能性がある。また、他の`@Service`のアノテーションがついたクラスに同じメソッドを書くと、片方の更新のし忘れが発生する。
所感
一見それっぽいパッケージ構成に見えますが、先人が問題点として挙げられていた通り、
- TestのPackage構成も記載したけど、実際は単体テストはほとんど書かれていない
- Entityと言いつつロジックが何もなく、実質DTO(Data Transfer Object) 状態になっている
XxxYyyService.java
という1つのファイルに、本来Domain Service
,Application Service
として切り出すべき処理が関係なく突っ込まれていて肥大化している- 依存の方向性の統制が取れていない
という感じでした。
PTS立ち上げ時
Package構成
(実際はMulti Moduleで構成されています)
app
└── src
├── main
│ ├── java/...
│ │ ├── domain
│ │ │ ├── entity
│ │ │ │ └── XxxYyy.java
│ │ │ ├── value_object
│ │ │ │ └── Zzz.java
│ │ │ └── repository
│ │ │ └── XxxYyyRepository.java
│ │ ├── presentation
│ │ │ └── controller
│ │ │ ├── XxxYyyController.java
│ │ │ ├── XxxYyyRequest.java
│ │ │ └── XxxYyyResponse.java
│ │ └── usecase
│ │ ├── XxxYyyUsecase.java
│ │ ├── XxxYyyParam.java
│ │ └── XxxYyyDto.java
│ └── resources/...
│ └── XxxYyyRepository.xml
└── test
├── java/...
│ ├── domain
│ │ ├── entity
│ │ ├── value_object
│ │ └── repository
│ ├── presentation
│ │ └── controller
│ └── usecase
└── resources/.../
└── XxxYyy.csv
所感
Package構成に OnionArchitecture を採用しており、以前のプロダクトの構成よりもPackageやClassの責務がわかりやすくなったと思います。
ただ、
- TestのPackage構成も記載したけど、実際は単体テストはほとんど書かれていない
- Entityと言いつつロジックが何もなく、実質DTO(Data Transfer Object) 状態になっている
XxxYyyUsecase.java
という1つのファイルに、本来 Domain の Entity や、Domain Service
に切り出すべき処理が関係なく突っ込まれていて肥大化している- Repositoryの引数と戻り値が統一されていない
という感じでした。
本当に重要でデザインパターンを駆使して実装されていた一部の処理についてはテストが書かれていました。
立ち上げ期は特にがんがんプロダクションコードを実装していくフェーズということもあり、
チーム内に浸透させるのが難しい、ビジネスが固まっていない中でガチガチにロジックを実装したくない、
などの理由で、他にも取り入れたかった改善施策があったそうですが見送ったようです。
結局改善したのはPackage構成だけということで、小さな一歩に見えますが、
この小さな一歩があったおかげでさらなる改善が進んだので、振り返ってみると非常に大きな一歩でした。
そのさらなる改善については後続の記事で詳述する予定です。
つづきはこちら (2025/3/7 追記)
ピンポイントタイム散布(PTS)でのバックエンド改善の軌跡 ~2. DDD, OnionArchitecture~ - OPTiM TECH BLOG
最後に
OPTiMではチームで協力し、難しく大きな課題を楽しみながらチャレンジしていきたいというメンバーを大募集しております。 ご興味がありましたら、下記フォームよりご応募ください。