第9回 プロジェクトに対する環境固有設定の導入

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

【求人のご案内】Javaアプリ開発エンジニア募集

よく訓練されたアップル信者、都元です。本連載はSpring Frameworkの解説が主ですが、今回はあまりSpringとは関係ない話をします。

システムを構成するためのあらゆる情報は、基本的にGit等の構成管理ツールの下で管理するのが望ましい、というのは一般的に共通の認識だと思います。しかし一方で、そのシステムを扱う全ての人に共通するわけではない設定、つまり環境固有の設定というのもあります。例えば、

  • ローカル環境での起動時
    • 接続すべきデータベースのホスト名、ポート番号、DB名、ユーザ名、パスワード
    • 仮にAWSを利用する場合、そのアクセスキーとシークレット
    • 仮にAmazon S3を利用する場合、そのバケット名
    • ログ設定ファイルのpath(ローカル起動でログ設定は各自自由に設定したいですよね)
    • Springの起動プロファイル(あ、Springに関係あった! 後で触れます。これも各自自由に設定したいものです)
  • AWS環境へのデプロイ作業時
    • デプロイ作業に利用するAWSのアクセスキーとシークレット
    • デプロイ先のリージョン
    • EC2インスタンスに対するSSHの秘密鍵ファイルのpath
    • RDSインスタンスのマスターユーザ名、パスワード

このような設定は、起動するローカル環境毎、デプロイ先のAWS環境毎に、必要な設定値が異なるでしょうし、そもそも秘密情報が含まれるのでリポジトリに載せたくないと考えます。

環境固有設定ファイルの外出しとインポート

というわけで、筆者はこのような環境固有設定を、Gradleの設定ファイル(build.gradle)の中から分離し、別のファイルとして管理しています。

.
├── .gitignore
├── build.gradle
├── env
│   ├── _sample.gradle
│   ├── dev.gradle
│   ├── prd.gradle
│   └── personal.gradle
(略)

build.gradleはメインとなるファイルで、envディレクトリの中にいくつかファイルがあります。また.gitignoreファイルがあり、_*.gradle 以外のファイルを全てGitの管理下から外しています。つまり、このディレクトリ内にある personal.gradle 等のファイルはGitの管理下にありません。

これらのファイルの中に、環境固有の設定を書くようにします。devにはAWS上の開発環境、prdにはAWS上の本番環境、personalは個人検証環境の設定を書いています。

この状態でbuild.gradle内で、下記のような記述を行うことにより、環境固有設定ファイルをインポートできます。

if (hasProperty('env') == false) { ext.env = 'personal' }
apply from: "env/${env}.gradle"

Gradleの起動時に ./gradlew bootRun -Penv=dev 等と指定することで、dev.gradleが読み込まれる仕組みです。特に設定しなかった場合は、personal.gradleが読み込まれることになります。

_sample.gradleには、どのような設定項目があるのか、解説と共に示しておくことで、環境固有設定ファイルを新たに作る場合に迷わずに済むようにしておきます。具体的には、こんな感じ。

ext {
  berserker = [
    local: [
      // アプリケーションがローカル環境から利用する、DBの接続情報
      database: [
        connectionString: "jdbc:mysql://localhost:3306/berserker?useLegacyDatetimeCode=false&serverTimezone=Universal",
        user: "root",
        password: ""
      ],
      // アプリケーションがローカルで起動する際の Spring profile名
      springProfile: "development,local",
      // アプリケーションがローカルで起動する際の logback 設定ファイル
      logbackConfigurationFile: "/Users/daisuke/.logback/berserker.xml"
    ]
  ]
}

環境固有設定値の参照方法

上記のような設定をインポートすると、build.gradle内では例えば berserker.local.database.user とすることでrootという値にアクセスできるようになります。

また、設定値はビルドスクリプト内だけでなく、アプリケーション内からも参照できる必要がありますね。このために、build.gradleの中で下記のような設定を記述します。

tasks.withType(org.springframework.boot.gradle.run.BootRunTask) {
  project.getSystemProperties().each { key, value ->
    systemProperty key, value
  }
}

Map<String, String> getSystemProperties() {
  return [
    'JDBC_CONNECTION_STRING':  berserker.local.database.connectionString,
    'DB_USERNAME':             berserker.local.database.user,
    'DB_PASSWORD':             berserker.local.database.password,
    'spring.profiles.active':  berserker.local.springProfile,
    'logging.config':          berserker.local.logbackConfigurationFile
  ]
}

アプリケーションの起動に必要な設定をMapで返すgetSystemPropertiesメソッドを定義し、これらの値をbootRunタスクによる起動時にシステムプロパティとして渡すようにしておきます。

システムプロパティの値は、下記の様にフィールドに対するDIされた値として参照できます。下記は正確には「環境変数→システムプロパティ→デフォルト値」の順で値を探すような記述です。

@Value("#{systemEnvironment['JDBC_CONNECTION_STRING'] ?: systemProperties['JDBC_CONNECTION_STRING'] ?: 'jdbc:mysql://localhost:3306/berserker'}")
String url;

詳しくはここ

サンプルアプリを実行してみる

いつものとおり、タグをチェックアウトして実行してみましょう。Spring Bootアプリケーションの起動はbootRunタスクから行います。

$ git clone https://github.com/classmethod-sandbox/berserker.git
$ cd berserker
$ git checkout 9.0
$ ./gradlew bootRun
:compileJava
:processResources
:classes
:findMainClass
:bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)

 (以下略)

起動したWebサーバにリクエストを送ってみます。

$ curl -v http://localhost:8080/
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 187
< Date: Fri, 08 Apr 2016 02:35:48 GMT
<
* Connection #0 to host localhost left intact
User(username=miyamoto, password=$2a$10$vxq4n5VB4bsgUlBK9DXV9edhX911Qz/5iYqjLi/6qZPp8Xl7ZACKC),User(username=yokota, password=$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm)

以前と同じようにDBアクセスできていますね。

Spring Frameworkが参照するシステムプロパティ

さて、少しはSpring Frameworkの話題に触れておこうと思います。Spring Frameworkが参照するシステムプロパティを一部ご紹介しておこうと思います。

まずspring.profiles.activeについて。Springには「起動プロファイル」という仕組みがあります。例えば「production」と「development」というプロファイルを定義したと仮定します。その上で、例えば下記のようなSpring bean定義をすると、起動プロファイルによって生成するBeanを切り替えられます。

@Bean
@Profile("production")
public Service service() {
  return new ServiceImpl();
}

@Bean
@Profile("development")
public Service dataSource() {
  return new MockService();
}

具体的には、メール送信のサービスクラスがあったとして、開発モードでは実際にはメールを送信しないようなモックに差し替えておく、といったようなことができます。

次にserver.port。これは分かりやすいですね。Webサーバが立ち上がるポート番号を指定します。デフォルトでは8080ですが、起動環境で常に8080に空きがあるかは分かりませんので、環境ごとに指定できると便利かもしれません。(上記の例ではそのように設定していませんが)

最後にlogging.config。これはログ出力フレームワークのLogbackの設定ファイルパスを指定するものです。

application.properties (application.yml)

Spring bootには、クラスパスのルートにあるapplication.properties(またはapplication.yml)を読み込んで、その設定値によってSpring bootによる自動設定を調整する仕組みがあります。

そして、前回Spring bootをご紹介した際には説明しませんでしたが、Spring bootにはDataSourceを自動的にBeanとして構成する仕組みも入っています。ので、DataSourceConfigurationクラス内でDataSource beanをわざわざ定義する必要はありません。ので削除してしまいます。

一方、そうするとDBのホスト名等の接続情報や、JDBCドライバのクラス名はどのように設定するのか、というと、これがapplication.propertiesなわけです。

このファイルには設定値をハードコーディングすることもできますし、下記のようにシステムプロパティを参照することもできます。

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=${JDBC_CONNECTION_STRING}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}

まとめ

なんだか、細かい雑多なことを説明するような回になってしまいましたw が、環境固有の設定値を、アプリケーションからも、ビルドスクリプトからも追い出して、一箇所にまとめることができるようになりました。

次回はFlywayによるDBマイグレーションの仕組みをご紹介しようと思っています。