設定値の外部化
このレッスンでは、接続先やポート番号といった環境ごとに変わる値を、コードから追い出す方法を身につけます。
ハードコードすると環境を移せません
データベースの接続先を Java のコードに直接書いてしまうと、開発機と本番で別々のソースを持つことになります。パスワードを書けば、そのままリポジトリに残って外へ出ます。環境によって変わる値は、コードではなく設定として外に置く。これが基本です。
Spring Boot は src/main/resources の下に置いた application.properties または application.yml を起動時に自動で読み込みます。同じ内容を 2 つの書き方で表すと次のようになります。
Properties
server.port=8080
app.name=shop
app.mail.from=noreply@example.com
app.mail.retry-count=3YAML
server:
port: 8080
app:
name: shop
mail:
from: noreply@example.com
retry-count: 3properties はキーが全部書かれていて検索しやすく、yml は階層が視覚的で長いキーの重複が減ります。どちらを選んでも読み込まれる中身は同じです。1 つのプロジェクトではどちらかにそろえてください。
取り出し方は @Value と @ConfigurationProperties
1 つの値をぱっと取り出したいときは @Value が手軽です。
Java
@Component
public class MailSender {
@Value("${app.mail.from}")
private String from;
@Value("${app.mail.retry-count:3}")
private int retryCount; // 設定が無ければ 3 になる
}角括弧ではなく波括弧とドル記号で囲む記法で、中に既定値も書けます。ただし値が増えてくると @Value は散らかります。関連する値をまとめて 1 つの型に束ねるのが @ConfigurationProperties です。
Java
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String from, int retryCount) {}この 1 行で app.mail.from と app.mail.retry-count が対応するフィールドへ入ります。ハイフンつなぎのキーがキャメルケースのフィールドへ結び付く点も便利です。型が付くので誤りに早く気づけますし、この型を注入すれば設定がどこで使われているかも追えます。設定が 3 つ以上あるなら、迷わずこちらを選んでください。
プロファイルで環境ごとに切り替えます
環境ごとの差分は、プロファイルという仕組みで分けます。application-dev.properties と application-prod.properties を用意し、起動時に有効なプロファイル名を指定すると、共通の application.properties に加えてその環境用のファイルが重ねて読み込まれます。同じキーがあれば、プロファイル側が勝ちます。
最後に勝つのは環境変数です
設定値の出どころは 1 つではありません。優先順位は大まかに、コマンドライン引数、環境変数、プロファイル別のファイル、共通のファイル、コード内の既定値の順で、上にあるものが下を上書きします。この順序があるおかげで、コンテナで動かすときにファイルを書き換えることなく、環境変数だけで接続先を差し替えられます。
キーの対応にも決まりがあります。app.mail.from に対する環境変数は APP_MAIL_FROM です。ドットをアンダースコアにして大文字にする、と覚えてください。パスワードや API キーは、ファイルではなくこの経路で渡すのが定石です。
演習で書くもの
Spring がやっている「設定ファイルを読んでキーで引き、無ければ既定値を使う」という処理を確かめます。ファイルの解析とコメント行・空行の読み飛ばしはひな形に用意済みです。キーで検索し、無ければ既定値を使う部分を書きます。
演習で使うひな形
parseProperties("app.name=shop\napp.name=store") は app.name の値が "store" の Map を返します。コメント、不正行、重複の処理は用意済みです。props.get(normalizedKey) で検索し、null のときだけ既定値を使います。登録された空文字はそのまま返します。
要件
- props.get(normalizedKey) で検索すること
- 結果が null なら既定値を返すこと
- 登録された空文字はそのまま返すこと