Spring BootのProfilesを使いこなす

Spring

まぁ便利ですよね、Profiles。アプリケーションの環境差分を吸収してくれる仕組みで、開発者のローカル環境、CI環境、ステージング環境、本番環境みたいな各環境での設定の差分を切り替える仕組みです。同じアプリケーション資材を使いつつも各環境での動作の違いを制御することができるアレですね。Spring Bootの機能も色々ありますがその中でもかなり利便性の高い機能ではないかと思います。

前提バージョン

今回の記事は以下のバージョンを前提にしています。

  • Spring Boot:2.0.3.RELEASE
  • kotlin:1.2.51

切替えられるもの

大きくいうとプロパティファイル(application.properties/application.yml)とBean定義を各環境で切り替えることが出来ます。Spring Bootでは様々な設定がプロパティの設定で制御可能なので、かなりの範囲の設定を環境依存値として使用することが出来ますね。

Appendix A. Common application properties

プロパティファイルの切替

Spring Bootでは、クラスパスルートのapplication-[Profile].propertiesまたはapplication-[Profile].ymlが有効になります。Profileなしのapplication.propertiesは常に有効であるため、環境毎に異なる設定値のみを各Profileのプロパティに記載することで、環境差分を吸収することが出来ます。

.
`-- src
    `-- main
        `-- resources
            |-- application-development.properties
            `-- application.properties

例えばこんな感じにプロパティファイルを配置しておけば、developmentプロファイルを有効にした場合にapplication-development.propertiesが有効になる、といった感じですね。

前述の通り、プロパティファイルで切り替えられる設定は多岐に渡り、各リソースのチューニング項目はもちろん、ログ出力パス・ログレベルやSpring Boot Actuatorのエンドポイントの制御など、柔軟な設定が可能です。

Bean定義の切替

Profilesを用いて環境毎にBean定義を切り替える事ができます。やり方は@Profileアノテーションを付与してプロファイルを指定するだけです。

@Bean
@Profile("development")
fun dataSource(): DataSource {
    return EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build()
}

この例ではProfilesにdevelopmentが指定された場合にDataSourceがBean定義される例を示しています。Java Configでは柔軟なBean定義が可能なため、設定ファイルのみでは難しいものも環境差分の設定として扱うことが出来ますね。

Profilesの指定方法

Spring BootにおけるProfilesの指定方法はテスト実行時の制御も含めてたくさんあります。詳細はリファレンスをご確認ください。

24. Externalized Configuration

テスト時の指定は除いてよく使われるものとしては、

  1. コマンドライン引数(--spring.profiles.active
  2. JVMオプション(-Dspring.profiles.active
  3. 環境変数(SPRING_PROFILES_ACTIVE

辺りでしょうか(上から優先)。環境構築の方針等によって都合の良いものを選択すれば良いと思います。

Profilesはカンマ区切りで複数指定可能です。
-Dspring.profiles.active=profileA,profileBと言った形。

アプリケーションでの参照方法

指定されているProfilesが何なのかをアプリケーションから参照することも可能です。ここでは、指定されているProfilesを取得するREST-APIを作成してみましょう。

@RestController("profiles")
class ProfileRestController(val environment: Environment) {

    @GetMapping
    fun getProfile(): Array {
        return environment.activeProfiles
    }

}

インジェクションしたEnvironmentから指定されているProfilesを取得しています。実行してみるとこんな感じになります。

まずはProfiles指定無しで起動。

java -jar ./build/libs/spring-profiles-sample-0.0.1-SNAPSHOT.jar

Profilesの指定をしていないため、defaultが指定されます。

2018-07-28 20:37:00.317  INFO 20768 --- [           main] c.e.s.SpringProfilesSampleApplicationKt  : No active profile set, falling back to default profiles: default

APIを実行してみます。

$ curl -H 'Content-Type:application/json' http://localhost:8080/profiles
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100     2    0     2    0     0     11      0 --:--:-- --:--:-- --:--:--    11[]

何も帰って来ません。

続いてdevelopmentを指定して起動。

java -Dspring.profiles.active=development -jar ./build/libs/spring-profiles-sample-0.0.1-SNAPSHOT.jar

Profilesにdevelopmentが指定されている旨が表示されます。

2018-07-28 20:43:22.612  INFO 21176 --- [           main] c.e.s.SpringProfilesSampleApplicationKt  : The following profiles are active: development

APIを実行してみます。

$ curl -H 'Content-Type:application/json' http://localhost:8080/profiles
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    15    0    15    0     0     95      0 --:--:-- --:--:-- --:--:--    95["development"]

とまぁこんな感じになります。

Environmentから取得したProfilesをBean定義しておけば○○モードで起動中、といった制御も簡単に出来ますね。

応用的な使い方

基本的なProfilesの制御方法をご紹介しましたが、ちょっとした応用的な使い方もご紹介します。

複雑なProfiles指定

Bean定義の切り替えに使用する@Profileアノテーションは、有効となるProfilesの指定しか出来ませんが、profileA、profileBともに指定されていない場合に有効、といった指定も可能です。

そのような指定にはConditionインターフェースの実装クラスと、@Conditionalアノテーションを使用します。

まずはConditionインターフェースの実装クラスの作成。

class SampleCondition : Condition {

    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        return !context.environment.acceptsProfiles("default", "development")
    }

}

ここでは、default、developmentがともに指定されていない場合にtrueを返すメソッドを定義しています。このConditionクラスを@Conditionalアノテーションで判定します。

@Bean
@Conditional(SampleCondition::class)
fun beanBySampleCondition(): String {
    return "beanBySampleCondition"
}

SampleConditionmatchesメソッドがtrueを返す場合のみBean定義が行われます。もう少し作り込めばより便利なやり方もできそうですね。

テストの実行をProfilesで制御する

Profilesの仕組みを使って環境差分を吸収しつつも、テストコードが実行される環境(開発者のローカル環境とCI環境など)の間で実行できるテストと実行出来ないテストが出てきてしまうような場合は、以下のような方法でテストの実行を制御することも可能です。

@Test
@IfProfileValue(name="spring.profiles.active",value = "development")
fun developmentTest() {
    println("Test on development profile.")
}

@IfProfileValueは、nameに指定したプロパティがvalueに指定した値である場合のみテストが実行されるアノテーションです。これを利用し、指定したProfilesに応じてテストの実行要否を制御することが出来ます。

環境変数でProfilesを指定している場合は使用出来ません。

サンプルアプリケーション

サンプルというほどサンプルではないのですが、説明したコードについてはこちらにあります。

kawakamitor/blog-materials
Contribute to kawakamitor/blog-materials development by creating an account on GitHub.

まとめ

もっと突き詰めると工夫できるやり方はたくさんあるのかもしれませんが、今回ご紹介したようなある程度基本的な仕組みを抑えておくととても便利に使えるのでは無いかと思います。

Spring Bootはクラウド環境でのアプリケーションや、マイクロサービスアーキテクチャと相性が良いと言われていますが、オンプレミス環境のWebアプリケーションにおいてもProfilesの仕組みは有用ではないかと思います。

この記事に対するコメント