我已经用硬编码的配置变量(如Google OAuth ClientId和ClientSecret)编写了我的中型Haskell应用程序。现在我正在为生产部署准备应用程序,我需要将所有这些配置变量从源代码中移到:(a)环境变量,或(b)纯文本配置文件。
以下是目前代码的样子:
googleClientId :: T.Text
googleClientId = "redacted"
googleClientSecret :: T.Text
googleClientSecret = "redacted"
generateOAuthUserCode :: IO (OAuthCodeResponse)
generateOAuthUserCode = do
r <- asJSON =<< post "https://accounts.google.com/o/oauth2/device/code" ["client_id" := googleClientId, "scope" := ("email profile" :: T.Text)]
return $ r ^. responseBody
从环境变量(或配置文件)中获取googleClientId
和googleClientSecret
的最快/最简单方法是什么?我尝试了以下方法:
googleClientId :: T.Text
googleClientId = undefined
googleClientSecret :: T.Text
googleClientSecret = undefined
main :: IO()
main = do
googleClientId <- getEnv "GOOGLE_CLIENT_ID"
googleClientSecret <- getENV "GOOGLE_CLIENT_SECRET"
-- Start the main app, which internally will call generateOAuthUserCode at some point.
我们的预期是全局googleClientId
和googleClientSecret
将被重新绑定,但我的编辑器立即开始显示一条警告,即"绑定遮蔽了现有绑定",这表明Haskell正在创建一个新绑定,而不是更改现有绑定。
所以,这里有两个问题:
- 首先是务实的。如何解决手头的问题,而不进入Reader monad,这可能涉及在我的应用程序中更改许多函数签名
- 第二,以学习为导向。Haskell有不可变的值,这是可以理解和欣赏的。它甚至有不可变的变量绑定吗?难道不可能像Common Lisp中那样获得动态变量绑定吗
编辑:以下方法如何?
下面的方法怎么样?
outerFunc :: String -> String -> IO ()
outerFunc googleClientId googleClientSecret = do
-- more code comes here
where
generateOAuthUserCode :: IO (OAuthCodeResponse)
generateOAuthUserCode = do
r <- asJSON =<< post "https://accounts.google.com/o/oauth2/device/code" ["client_id" := googleClientId, "scope" := ("email profile" :: T.Text)]
return $ r ^. responseBody
-- more functions depending upon the config variables
我假设在代码库中经常依赖像googleClientId
这样的全局变量。
在走"技术债务"路线之前,您可能需要尝试并至少估计Reader
方法的成本。无论如何,全局变量是一种糟糕的做法,@Carsten提出了另一种重构方法。但既然你在寻求务实的帮助
问题1:最快/最简单的方法是使用不受欢迎的unsafePerformIO
。像这样:
googleClientId = unsafePerformIO $ getEnv "GOOGLE_CLIENT_ID"
main = putStrLn googleClientId
这基本上允许您忽略IO
的安全性,并将所需的值放入全局变量中,就像它是一个普通字符串一样。请注意,如果环境变量不存在,getEnv
将崩溃。
问题2:不能在Haskell中"更新"变量。如果在嵌套作用域中创建另一个同名变量,则该绑定将在内部作用域中覆盖外部变量,使外部绑定保持不变。这有点令人困惑,因此发出了警告。