前置き
現在参画中のプロジェクトで、あまりに既存実装がフレームワークのお作法を無視しており、スケジュールに余裕がないとそれを踏襲してしまいがちな傾向があるため、怒りに身を任せてエントリを作りました。
特定のプロジェクト向けに書いたので、汎用的にどのプロジェクトにも当てはまるわけではないし、異論はウェルカムです。
書きたいことはたくさんあるのですが、雑多に書いても頭に入らないので、今回はプロジェクト構成にかかわる部分で特におさえて欲しいことベスト3を紹介します。
環境
- Spring Boot 2.0.6
前提とするプロジェクトのクラス構成
以下を正解として実装しているのだが、ガン無視のいにしえのクラスがあって、あるべき姿に近づけるのを阻害している状況。
気をつけて欲しいこと!(本題)
各レイヤー層の役割を守る
上の図でいうと私は以下のような感じで使い分けしています。
- Controller: リクエストの入口。htmlへ表示させるためのデータを渡したり、リクエスト時に渡されてきたデータを受け取る場所。分岐のある複雑な処理は基本的には書かない。
- Service: ビジネスロジックの母体。基本的にはサーバ処理と呼ばれるものをはここに実装するイメージ。
- Logic: Service同士で共通の処理をまとめるための場所。Service がビジネスロジックの母体と言いつつ共通処理は多々あるので、ここに実装するほうが多い印象。
- Utility: ここも共通処理だが、DBアクセスが不要なもの(ex.文字列の加工など)
- Repository: DBアクセスのための入口。複雑な処理はここに実装せずにService か Logicに。
逆に実装してて違うなぁと思う例としてはこんなのがあります1)基本的に実話
- Controllerでの分岐がいっぱい。そのメソッド、Serviceで実装して呼び出したほうがいいんじゃない?
- LogicにDBアクセスのないメソッドがいる。それはUtilityで十分では?
- Logic作ってないからいろんなServiceに同じ処理がある。最初にコピー実装した人が共通化しておいてくれれば苦労しなかったのに。
- なんかDBの結果が想定外だと思ったら、Repositoryになぞの分岐がある。ツライ。
このあたりはプロジェクトによっても変わってくるかと思うので目安ですが、少なくとも「役割分担がない」なんてことは無いはないはずので、開発しているプロジェクトでの役割分担は守りましょう。
レイヤー間の相関関係を無視しない
頼むから上の図の矢印を守ってくれ(切実)
矢印が守られない = 1つ目のレイヤー層の役割も無視されがちなので、合わせて気をつけて欲しいです。
たぶん、1つ目の役割を守れば、間違った矢印の方角になることはないはず。
よく見る間違っている例としては、こんな感じでしょうか。
- LogicからService呼び出す(逆になってる)
- ControllerからLogic呼び出すだけでは飽き足らずRepositoryを呼び出してしまう(どうなるんやトランザクション。詳細は3つ目で)
- ServiceからServiceを呼び出す(最悪許容しますけどね・・・)
基本的には、入口がControllerでさえあれば動いてしまうのが、悲しいところです。
使い捨てプロジェクトならそれでもよいですが、長く続くプロジェクトでは間違った矢印が不具合のもと。きっとプロジェクトの役割を守れば自動的に守られているはずなので気をつけてください。
トランザクションスコープに気をつける
Spring Bootでは@Transactionalのアノテーションを付けたクラス・メソッドの単位でトランザクションが貼られます。
だいたいServiceクラスにアノテーションをつけるケースが多いと思いますが、意識してほしいケースがあるので一つ紹介します。2)トランザクションを効かせるためには条件があるのですが、このエントリでは割愛。
それは、Controllerで何回もServiceのメソッド呼んでるけど、トランザクション別なのわかって実装してるよね?というケース。
実際のコードを見ないとわかりにくいので書いちゃいます。
@Controller public class SampleController { @Autowired private SampleService service; @RequestMapping public void updateHoge() { service.insertA(); service.updateB(); } }
@Service public class SampleService { @Transactional public void insertA() { ・・・ } @Transactional public void updateB() { ・・・ } }
insertAとupdateBは別トランザクションなんだけど、この例だときっとトランザクション1つのほうが良さそう。
そもそも気をつけてほしい1つ目を守れば、こんな処理はControllerに書かないはず(と信じたい)
終わりに
改めて読み直すとめちゃくちゃ初歩的なことを書いたような気もするのですが、スケジュールが切羽詰まってくると見えなくなってしまうのがプロジェクト開発の恐ろしいところ。
なにかの役に立てば幸いです。
ふだん無意識で実装していることを言葉にするのは難しいですが、言いたいことが溜まってきたら、その2のエントリを書くかもしれません。
(エントリ作ってからずいぶん時間が経ってしまって熱量が薄れてきたので、エントリ削除しようか30回くらい迷ったけど、もったいないので公開したのはナイショ)