解決 Databend 命令行參數(shù)加載問題
前言
Iteration 11[1]?從 4/9 開始到 4/22 結(jié)束,為期兩周。
這個(gè)周期非??鞓罚以炝艘欢演喿觼斫鉀Q Databend 的命令行使用體驗(yàn)問題:
serde-bridge[2]:將一個(gè)值在不同的 serde 實(shí)現(xiàn)中進(jìn)行轉(zhuǎn)換
serde-env[3]:支持將環(huán)境變量解析為嵌套的結(jié)構(gòu)體
serfig[4]:基于 serde 實(shí)現(xiàn)的多層配置系統(tǒng),支持從環(huán)境變量,配置文件,自身等多個(gè)地方讀取并合并配置
最終實(shí)現(xiàn)的效果是 Databend 能夠按照指定的順序依次加載來自配置文件,環(huán)境變量和命令行參數(shù)中的配置:
背景
通過命令行參數(shù)配置:Databend 經(jīng)歷早期的野蠻生長(zhǎng)之后,現(xiàn)在終于有時(shí)間可以稍微打磨一下使用體驗(yàn)。首當(dāng)其沖是繁復(fù)而不成體系的配置項(xiàng),以配置 S3 存儲(chǔ)的 Bucket 為例:
通過命令行參數(shù)配置:
通過環(huán)境變量配置:
通過配置文件配置:
出現(xiàn)這種狀況的一大原因是 clap 的不良設(shè)計(jì)導(dǎo)致用戶使用中出現(xiàn)的畸形姿勢(shì):
clap 的?Parser?不支持結(jié)構(gòu)體,所有 args 都必須平鋪,導(dǎo)致用戶必須為所有的結(jié)構(gòu)體加上?#[clap(flatten)]:
更糟糕的是,clap 依賴字段名來唯一區(qū)分參數(shù),這就要求整個(gè)結(jié)構(gòu)體中不得出現(xiàn)重名的字段。比如下列這樣的代碼能編譯,但是無法正常運(yùn)行:
所以大家開始寫這樣的代碼:
Args 與 Env 的關(guān)系已經(jīng)非?;靵y了,databend 還需要支持從配置文件中加載。為了保障正確的加載順序,社區(qū)甚至開始寫宏來強(qiáng)行再次加載環(huán)境變量:
思考
在動(dòng)手改進(jìn)之前,首先考慮最理想的狀況是怎樣的:
??正確的加載順序:同名字段會(huì)按照 config -> env -> args 的順序記載,后者覆蓋前者
??統(tǒng)一的命名體系:同一個(gè)字段在不同地方使用統(tǒng)一的命名風(fēng)格,比如說 storage.s3.bucket, --storage-s3-bucket, STORAGE_S3_BUCKET
??減少冗余代碼:盡可能減少維護(hù)者需要寫的重復(fù)代碼
社區(qū)在 Issue?bug: config overwrite when specify --config and any other command line args.[5]?中貢獻(xiàn)了一個(gè) idea:將?config-rs[6]?與 clap 結(jié)合起來,讓 clap 能夠作為 config-rs 的一個(gè) Source。我為 config-rs 提交了?proposal: Implement serde::Serializer and Source/AsyncSource for Value[7],但是在嘗試實(shí)現(xiàn) demo 的時(shí)候遇到了無法解決的問題,以至于我開始覺得我們需要新的方法和新的思路。
好,跳出來思考這個(gè)問題:
配置加載實(shí)際上就是按照順序從不同的地方加載數(shù)據(jù),解析成我們的 Config 結(jié)構(gòu)體并進(jìn)行合并的過程。所以我們需要:
將環(huán)境變量解析為嵌套的結(jié)構(gòu)體
一個(gè)統(tǒng)一的數(shù)據(jù)表示方式
將來自不同的地方的數(shù)據(jù)進(jìn)行合并? 實(shí)現(xiàn)? 實(shí)現(xiàn)
serde-env
最開始我嘗試使用了?envy[8],但是它不支持將環(huán)境變量解析為嵌套的結(jié)構(gòu)體,為此我開發(fā)了?serde-env[9]:
思路其實(shí)很簡(jiǎn)單,serde-env 內(nèi)部將環(huán)境變量表示為使用?_?分隔的 tree,于是上述例子中的 Test.cargo.home 實(shí)際上就能轉(zhuǎn)化為 CARGO_HOME。
延續(xù)這樣的思路,serde-env 還能夠支持形如這樣的結(jié)構(gòu)體:
有效解決了環(huán)境變量轉(zhuǎn)化為結(jié)構(gòu)體的問題。
serde-bridge
為了能夠處理配置之間的合并,我開發(fā)了?serde-bridge[10]:
它是一個(gè)到 serde API one-to-one 的 mapping,跟?serde-value[11]?相似,但是更加完整,同時(shí)實(shí)現(xiàn)了?{De,S}erialize[r]?等類型。任何 serde 實(shí)現(xiàn)都可以基于?serde_bridge::Value?作為中間層來進(jìn)行轉(zhuǎn)換。
serfig
在上述庫的支持下,serfig 通過 serde_bridge::Value 來合并配置并對(duì)外暴露 Builder 的接口:
跟 clap 的整合也非常容易,強(qiáng)大的 serde_bridge::Value 使得我們能夠?qū)⒔Y(jié)構(gòu)體本身也作為一個(gè)數(shù)據(jù)源 from_self,以 Databend 為例:
我們首先使用 Config::parse() 來加載命令參數(shù),然后在最后使用 from_self(arg_conf) 來覆蓋前面獲取到的數(shù)據(jù)。
后續(xù)
目前的實(shí)現(xiàn)還有不少問題,我們?nèi)晕唇鉀Q?#[clap(flatten)]?導(dǎo)致的各種問題:
??相同的字段還是會(huì)沖突??需要手動(dòng)指定 clap 的?long?字段未來可能會(huì)想辦法自行實(shí)現(xiàn) clap Parser 來徹底解決這些不一致的問題。
總結(jié)
快樂的造輪子周期,以至于這周一直在發(fā)?#今天用?而不是?#今天學(xué),下個(gè)周期還是要多輸入一些東西~
引用鏈接
?Iteration 11:?https://github.com/users/Xuanwo/projects/2/views/1?filterQuery=iteration%3A%22Iteration+11%22
?serde-bridge:?https://github.com/Xuanwo/serde-bridge
?serde-env:?https://github.com/Xuanwo/serde-env
?serfig:?https://github.com/Xuanwo/serfig
?bug: config overwrite when specify --config and any other command line args.:?https://github.com/datafuselabs/databend/issues/4362
?config-rs:?https://github.com/mehcode/config-rs
?proposal: Implement serde::Serializer and Source/AsyncSource for Value:?https://github.com/mehcode/config-rs/issues/315
?envy:?https://github.com/softprops/envy
?serde-env:?https://github.com/Xuanwo/serde-env
?serde-bridge:?https://github.com/Xuanwo/serde-bridge
?serde-value:?https://github.com/arcnmx/serde-value
關(guān)于 Databend
Databend 是一款開源、彈性、低成本,基于對(duì)象存儲(chǔ)也可以做實(shí)時(shí)分析的新式數(shù)倉(cāng)。期待您的關(guān)注,一起探索云原生數(shù)倉(cāng)解決方案,打造新一代開源 Data Cloud。
Databend 文檔:https://databend.rs/
twitter:https://twitter.com/Datafuse_Labs
Slack:https://datafusecloud.slack.com/
Wechat:Databend
GitHub :https://github.com/datafuselabs/databend
