日々常々

ふつうのプログラマがあたりまえにしたいこと。

Spring Bootの開発時サービスサポート(Testcontainers & Docker Compose)のサンプル実装と所感

Spring Boot 3.1でTestcontainersとDocker Composeのサポートが導入されました。 導入時はそれぞれ独立した項目でしたが、現在(Spring Boot 3.4.4)では 開発時のサービスDevelopment-time Services)としてまとめられています。

TestcontainersやDocker ComposeはそれぞれSpring Bootのサポートがなくても使われてきましたが、 サポートが導入されたことで非常に使いやすくなりました。

Spring Bootがサポートしてくれることを簡単に言うと「テストやローカル実行時に勝手にOCIコンテナを立ち上げて接続設定も面倒見てくれる」という機能です。 これがなければ自身(もしくは外部の仕組み)でコンテナを立ち上げて、その設定に従ってSpring Boot側に接続設定を行う必要がありました。 ちょっと楽になります。このちょっとさがとても大事。(だからと言って「テストでもどんどんコンテナ使いましょう!!」とはならない点には注意が必要ですが、それは別の話。)

それぞれが独立したツールであり、実際に併用されてきため、動かす方法は幾通りもあります。 それだけに情報も錯綜するため、何をどう使ったら動くか(また動かないか)がわかりづらくもあります。

なので極力シンプルかつ実プロダクトで使う場合のイメージで動くものを書いておこうと思いました。

本体は以下のリポジトリにあります。このブログはREADMEの抜粋です。

github.com

Testcontainers

ざっくり導入手順

  • 依存に testAndDevelopmentOnly("org.springframework.boot:spring-boot-testcontainers") を追加
  • 使用するコンテナに応じたTestcontainersモジュールを依存に追加
    • ここでは org.testcontainers:mysql を追加
  • @TestConfigurationServiceConnection をBean定義して、テストで使う
    • @Bean
      @ServiceConnection
      MySQLContainer<?> mysql() {
          return new MySQLContainer<>("mysql:lts");
      }
      

メモ

Spring Bootではよくあることなのですが、Spring Bootサポートがある場合、使用しているツールの標準の方法より簡潔な選択肢が出てきます。

TestcontainersではJUnit5サポート( org.testcontainers:junit-jupiter )やJDBCサポートとしてもJDBC URLとコンテナオブジェクト( MySQLContainer など)が提供されています。 JDBCサポートではJDBC URLのほうがURLを変えるだけで使用できる高レベルAPIになるため、TestcontainersでJDBC接続先のコンテナのみを使用する場合はJDBC URLが推奨されていたりします。

しかしSpring Bootのテストでは、このサンプルで示しているようにJDBC URLもJUnit5サポート機能( org.testcontainers:junit-jupiter )も使用せず、 ここで実装しているようにコンテナオブジェクトを @ServiceConnection で使用し、合成アノテーションを作成するのが適切です。

Spring Bootのリファレンスでは @Container@ImportTestcontainers を使用するものも紹介されていますが、 ちゃんと読めばこれはTestcontainersを昔から使っているなど、この使い方にこだわりのある方向けだということがわかります。ちゃんと読むの難しいとかは、まぁ、わかる。

ローカル実行はJDBCだけでコンテナを使用するのであればJDBC URLで良いと思います。 TestMyApplication の作りは癖がありますし、ローカル実行とはいえテストリソースがクラスパスに含まれる点に若干の懸念があるためです。 JDBC以外も使うのであればコンテナオブジェクト+@ServiceConnection に寄せるか、他のコンテナ起動方法にした方がいいと思います。

Docker Compose

ざっくり導入手順

  • compose.yml を用意
  • 依存に testAndDevelopmentOnly("org.springframework.boot:spring-boot-docker-compose") を追加
  • テストで使う場合は spring.docker.compose.skip.in-tests=false を指定

メモ

compose.yml の検出はworking directoryを起点に行いますが、working directoryは実行の仕方によって変わります。 たとえばMyApplicationIDEで実行した場合、未設定だとルートプロジェクトがworking directoryになったりしますが、 テストを実行した場合はプロジェクトルートがworking directoryになったりします。

このサンプルではマルチプロジェクト構成で動作を安定させるために spring.docker.compose.file=classpath:dev-compose.yml のようにクラスパスで指定しています。

テストでは classpath:test-compose.yml を使用しているのは別設定も使用できることを示していますが、Docker Composeの設定が複数になるのも微妙ですし、 同じにできるなら同じものを使う方が好ましいです。 クラスパスに含める形にする場合、 main に置くとリリースモジュールに含まれることになるのは少し気になるところです。 動きはしないので害はありませんが、気になるなら除外しましょう。

Docker Composeを開発時のアプリケーション起動やテスト時以外でも起動するのであればデフォルトのままworking directoryを使用するとして、 うまく動作しない時はworking directoryを設定するか、 spring.docker.compose.file絶対パスで指定するのが良いでしょう。

所感

「ざっくり導入手順」を見て分かるように、簡単に使えるのはDocker Composeの方です。 一方でTestcontainersの方が柔軟で痒いところにも手が届きそうな感じです。

Docker Composeサポートはシンプルケースでは org.springframework.boot:spring-boot-docker-compose を依存に入れるだけで済み、 ServiceConnection などは表に出てきません。 すでにDocker Composeを使っている場合、依存を追加するだけで動作しかねません。 楽なのはいいですが、設定が必要な状況になると急に難度が上がるように感じるかもしれません。

TestcontainersサポートはTestcontainersの Container を使って @ServiceConnection も書くので、うまくいかなくてもこの延長線上でなんとかなる感はあります。 スライスをテストする場合などは 必要なコンテナのみを使用するのも簡単です。 Testcontainers単体で使う場合でも複数通りの使い方があり、さらにSpring Bootのドキュメントでもいくつかの使い方が示されているため、 どうやったら動くのかわかりづらいかもしれませんし、効果のない組み合わせで使ったりとかも散見されます。

ローカル実行はサポート機能は使わなくていいかなぁというのが正直なところですが、使うならDocker Composeかなと思います。 ただテストで使っていない場合にローカル実行のために依存を追加するかというと正直微妙なところ。 TestcontainersはJDBC URLなら使いたいですが、JDBC以外には使えませんし、名前に冠している通りテスト向きで、ローカル実行ではどうしてもぎこちない感じが拭えません。とはいえ併用も微妙なので、テストでTestcontainersを使っているなら使ってもいいかなぁ、くらい。忌避するほどのものではない感覚です。

補足

  • TestcontainersのバージョンもSpring BootでManagementされています。Testcontainersに限らず、Spring BootがManagementしているバージョンはよほどの事情がない限り自分で指定しない方がいいです。
  • Gradleの testAndDevelopmentOnlySpring Boot 3.2で追加されました。Testcontainersのためだそうですが、他でも使えるかもですね。

あとがき

本体がコードとREADMEなのでGitHubにあげるだけで満足なんだけど、ブログにも書いてみるってのはどうかなと。

新しい(って言っても3.1は2024-11-14なので半年前。……新しい、でいいか。)機能は機能自身も情報も安定しませんし、なかなか取り扱いが難しいものです。 開発時サービス(TestcontainersやDocker Compose)は冒頭で書いているようにSpring Bootに機能がなくとも使われてきたものですが、Spring Bootサポートなしでの使い方とありでの使い方が入り混じったり、なかなかにわかりづらい状態。説明するにも動くコードが居るなぁと。

3.4まできてそれなりに実績と落ち着きも見えてきていると思います。この辺りで一度足場固めをして、半端な状態の使い方を脱するのがよいんじゃないでしょうか。動くものだけを見れば、どちらであってもだいぶシンプルなものです。動くものだけを見れば。実務で使うならそれぞれREADMEに書いたくらいは理解して使って欲しく思います。

自分たちで使う道具の点検について書いている本ってあるっけーと本棚を眺めていたらプロダクティブプログラマが目に入りました。

パラパラ読み返すと十五章で紹介されている「イカダのイカリ」アンチパターン(余計な荷物を背負っている状態。ゴールデンハンマー症候群とかも近いやもしれない。)などがうっすら関連するでしょうか。15年以上前の書籍になりますが、あまり年代は気にしなくていい本の一つです。

アプレンティスシップ・パターンでは「具体的スキル」あたりでしょうか。

「なんとなくやったら動いた」で一旦よしとしてしまいがちなものたちですが、足場になるところはしっかり固めていきたいなぁ。って感じです。