🚀

Rocketでデータベース設定を環境によって切り替える

2023/09/25に公開

設定ファイルと環境変数

ここに書いてありますが、Rocket は Rocket.toml というファイルで設定を管理できます。また、環境変数もROCKET_xxxxという形式にすることで、Rocket.toml の値を上書きすることができます。そして、環境変数にROCKET_PROFILE=devなどとすることで、プロフィールに該当する設定が有効になります。また、この設定ファイルと環境変数の仕組みはFigmentというライブラリをベースに作られています。

Rocket.toml

Rocket.toml は下記のような感じで設定できます。[default]とか[local]とかがプロフィールです。defaultは全プロフィールで適用されるデフォルト値です。下記の場合、localprodというプロフィールがあり、どちらのプロフィールでもaddresslimitsは同じということになります。hogedatabase.hogeについては、プロフィール毎に異なります。

Rocket.toml
[default]
address = "0.0.0.0"
limits = { form = "64 kB", json = "1 MiB" }

[local]
hoge = "local hoge"

[local.databases.hoge]
url = "postgres://user:password@localhost/hoge"

[prod]
hoge = "prod hoge"

[prod.databases.hoge]
url = "postgres://user:password@prod/hoge"

環境変数

.env などで下記のように環境変数を設定することで、上記の Rocket.toml を上書きできます。

.env
ROCKET_PROFILE=dev
ROCKET_HOGE="env hoge"
ROCKET_DATABASES={hoge={url=postgres://user:password@env/hoge}}

上記で、ROCKET_DATABASES_HOGE_URL=postgres://user:password@env/hogeといったような設定方法は、試してみたけどできなそうでした。データベースが複数ある場合などは、結構煩雑になるかもしれません。

main.rs で取得してみる

main.rs
#[macro_use] extern crate rocket;
use rocket::Config;
use serde::Deserialize;
use dotenv::dotenv;
use rocket::figment::value::Map;

#[derive(Deserialize, Debug)]
struct MyConfig {
    hoge: String,
    databases: Map<String, Map<String, String>>
}

#[get("/")]
fn index() -> String {
    let config = Config::figment().extract::<MyConfig>();
    format!("{:?}", config)
}

#[launch]
fn rocket() -> _ {
    dotenv().ok();
    rocket::build()
        .mount("/", routes![index])
}

レスポンスは下記のようになります。

Ok(MyConfig { hoge: "env hoge", databases: {"hoge": {"url": "postgres://user:password@env/hoge"}} })

コードで設定値を使う

下記のような感じで、rocket::build().attach(AdHoc::config::<AppConfig>())とやると、ハンドラ関数内で使えるようになりました。便利な気がします。まあ、dotenv とか使うだけでもいい気もします。ハンドラ関数から usecase -> repository に必要となる設定値・DB Pool などを全部渡すような形になると、渡すのがめんどくさいというのは結構大きな問題としてあるのですが、まあ確かに、usecase, repository で色々考えなくて済むというのもあるかなーと思いました。せっかく Rocket はハンドラ関数の引数に設定するところまでは、超簡単にできるようになっているので、それを使って、usecase -> repository には順番に渡していこうかなと思っております。

main.rs
#[macro_use] extern crate rocket;
use serde::Deserialize;
use dotenv::dotenv;
use rocket::figment::value::Map;
use rocket::{State, fairing::AdHoc};

#[derive(Deserialize, Debug)]
struct AppConfig {
    hoge: String,
    databases: Map<String, Map<String, String>>
}

#[get("/")]
fn index(config: &State<AppConfig>) -> String {
    config.databases.get("hoge").unwrap().get("url").unwrap().to_string()
}

#[launch]
fn rocket() -> _ {
    dotenv().ok();
    rocket::build()
        .mount("/", routes![index])
        .attach(AdHoc::config::<AppConfig>())
}

Discussion