iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Software Development

Rust Web API 從零開始系列 第 15

Day15 - 管理應用程式的組態

  • 分享至 

  • xImage
  •  

目前的應用程式中的參數像是database的連線字串都是直接寫死的,這樣其實是不利於測試與佈署的。實際上一個應用程式往往需要隨著環境不同帶入不同的組態,舉例來說在測試環境與正式環境使用不同的database做區隔,此外假設因為一些考量要更換使用的第三方服務,因為這些寫死的參數就會讓更換變得困難。一般而言,我們會用環境變數的方式將參數傳入應用程式中,像是:

use std::env;

let val = env::var("MY_VARIABLE");

然而這其實也是一個壞味道,直接在組間中取得靜態資源的方式,會讓組件依賴於該環境,也降低了測試的可能性,在rust中我們可以使用config這個套件來組織我們的組態設定。

cargo add config

建立configuration.rs用來組織系統組態,用來集中所有讀取設定的部份。config套件曾經有過一次版本更新,新的api改用builder模式去建立整個組態:

//// configuration.rs
let settings = Config::builder()
    //// configuration_directory表示設定檔存放的位置,required用於設定該筆設定檔是否為必要
    .add_source(File::from(configuration_directory.join("base")).required(true))
    //// environment表示當前的環境設定檔
    .add_source(File::from(configuration_directory.join(environment.as_str())).required(true))
    //// 讀入環境設定以建立組態
    .add_source(
            config::Environment::with_prefix("APP")
                .try_parsing(true)
                .separator("__"),
        )
        .build()?;

這邊使用add_source方法來加入各種組態,config套件支援環境變數、json、yaml、toml等等多種檔案格式,並且由加入的順序,新加入的設定會覆蓋舊的設定。以應用程式來說,我會將可以公開的設定放在yaml中,而需要保護的設定則是以環境變數的方式設定,後續直接放在運行環境的隱私變數中。

保護連線字串

接下來要建立一個結構讓我們可以用強型別的方式使用系統組態,另一方面這也是對系統設定的一種抽象形式。此外連線字串是需要被保護的資料,不可以直接暴露在應用程式中,因此我們要使用secrecy這個套件,它可以好好保護我們的機密資料:

cargo add secrecy

接下來在configuration.rs中定義Settings struct

//// configuration.rs
#[derive(Deserialize, Clone, Debug)]
pub struct Settings {
    pub database: DatabaseSettings,
}

#[derive(Deserialize, Clone, Debug)]
pub struct DatabaseSettings {
    pub connection_string: Secret<String>
}

最後在config builder組織好設定後把它轉型成我們設定的型別

//// configuration.rs
settings.try_deserialize::<Settings>()

到此組織系統組態的部份就完成了,接下來我們看一下如何使用。

expose_secret方法

接下來調整我們的路由

//// application.rs
pub fn build(config: &Settings) -> Router {
    let database = Database::connect(settings.database.connection_string.expose_secret())
        .unwrap();
        
    let app = Router::new()
        .route("/", get(health_check))
        .route("/subscriptions", post(subscribe))
        /// 在路由中加入swagger
        .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
        .with_state(AppState {
            database
        });;
    app
}

我們從main.rs中取得組態設定,並作為參數傳遞到build方法中,由於connection_string被保護了,需要額外調用expose_secret方法才能使用。透過Secret保護的資料除非主動調用,否則無法被知道,這個設計可以防止重要資料不小心被暴露在應用程式的log之中。

小結

透過組織的方式來統整系統中的設定,可以讓我們的系統更為嚴謹,也透過這個方式讓組件不直接依賴於環境變數與設定文件,整個建立config的部份可以參考這裡


上一篇
Day14 - 幫API加上OpenAPI文件吧
下一篇
Day16 - 發佈之前,先加個Log吧(1)
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言