今さら聞けないMaven – インタフェースと実装のスコープを分ける

| 3 min read
Author: toshio-ogiwara toshio-ogiwaraの画像

インタフェースと実装のモジュール(jar)が分かれていてアプリはインタフェースしか使ってはいけないハズだったがウッカリ実装側のクラスを使っていたという経験はないですか?

このようなことを防ぐにはCheckStyleやArchUnitなどの静的解析ツールで検知することや属人的にコードレビューで指摘するといった方法も考えられますが、一番リーズナブルなのはdependencyのスコープをcompileとruntimeに分けて定義することです。

インタフェースと実装を分けて使いたい例としてよくあるのがJakarta Persistence(JPA)だと思いますので今回はその実装のHibernateを例にどのようなことかを説明してみます。

丸っとcompileスコープの問題

#

Hibernateを使うがAPIの依存はJPA/JTA(Jakarta Transactions)に留めるといったお題があった場合、皆さんはpomのdependencyをどのように定義しますか?もしかして、こんな感じでHibernateだけしか定義しなかったりしませんか?

<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.6.Final</version>
</dependency>

dependencyにはscopeが必要ですが、この例では省略しているのでscopeはデフォルトのcompileになります。ですので、この定義だけでもなに不自由なく実装や実行をすることできます。ガっ、しかし問題は不必要なモノが混じる可能性があることです。

hibernate-coreの依存関係は次のようになっています(今回関係する部分だけ記載)

dependency

今回のお題を思い出してもらうとAPIの利用はJPA/JTAのみにしたいハズです。ですがhibernate-coreのスコープをcompileで定義しているため、hibernate-coreのすべてのクラスが参照できるようになってしまっています。

VSCodeやEclipseなどのIDEの入力補完にもhibernate-coreのクラスがでてくるため、JPAのクラスと勘違いしてポチッとしてしまい、あげく気がつかないといったこともあります(そしてテストも後半になった頃に、気がついてヒィィ~となることに、、、)

capture1

スコープを分けて改善

#

今回のお題のようにインタフェースと実装とでモジュールが分かれていて、かつインタフェースだけに依存するようにしたいといった場合、次のようにインタフェース側のモジュールをcomipleスコープで定義し、実装側はruntimeで定義することで実装側のクラスを直接参照させないようにすることができます。

<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.6.Final</version>
<scope>runtime</scope>
</dependency>

comipleスコープはコンパイル時にその依存が必要なるのに対して、runtimeスコープは実行時だけに必要なスコープとなるためコンパイル時にその依存は含まれません。このためrutimeスコープのクラスがimportに含まれていた場合はコンパイルエラーになりますし、そもそもIDEの入力補完の候補としても出てこなくなります。

capture1

このデメリットとして丸っとcompileスコープでは書かなくてもよかった推移的依存のdependencyを書かないといけないことがあります。これはこれで面倒くさい上にpomが大きくなりがちですが、インタフェースだけに依存させるというお題があるのであれば分けて定義しておく方が絶対に無難です(経験者は語る・・

豆蔵デベロッパーサイト - 先週のアクセスランキング
  1. 基本から理解するJWTとJWT認証の仕組み (2022-12-08)
  2. Docker+Wasm で WASM をコンテナとして実行する (2023-01-25)
  3. 自然言語処理初心者が「GPT2-japanese」で遊んでみた (2022-07-08)
  4. 直感が理性に大反抗!「モンティ・ホール問題」 (2022-07-04)
  5. Nuxt3入門(第4回) - Nuxtのルーティングを理解する (2022-10-09)
  6. AWS認定資格を12個すべて取得したので勉強したことなどをまとめます (2022-12-12)
  7. Jest再入門 - 関数・モジュールモック編 (2022-07-03)
  8. ORマッパーのTypeORMをTypeScriptで使う (2022-07-27)
  9. Nuxt3入門(第8回) - Nuxt3のuseStateでコンポーネント間で状態を共有する (2022-10-28)
  10. Nuxt3入門(第1回) - Nuxtがサポートするレンダリングモードを理解する (2022-09-25)