Haskell Servant API启动时加载配置,避免重复读取数据库连接串
在Servant中实现启动时加载数据库配置(无需第三方缓存包)
核心思路
和Java/C#的DI单例思路一致:在应用启动阶段一次性读取配置并加载到内存,之后所有请求直接复用内存中的配置实例。Servant本身支持通过传递环境参数或直接绑定配置到处理函数的方式实现,完全不需要额外缓存包。
具体实现步骤
1. 定义配置类型
先把配置文件的结构映射成Haskell数据类型,方便解析和使用:
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} import GHC.Generics import Data.Aeson (FromJSON, decodeFileStrict) import Servant import Database.PostgreSQL.Simple (Connection, connectPostgreSQL, query, Only(..), close) import Control.Monad.IO.Class (liftIO) -- 对应配置文件的结构,这里只保留连接串字段 data DBConfig = DBConfig { dbConnString :: String } deriving (Generic, Show) -- 让配置类型支持从JSON文件解析 instance FromJSON DBConfig
2. 启动时加载配置
在main函数中一次性读取配置文件,失败则直接终止应用:
-- 读取并解析配置文件 loadConfig :: IO DBConfig loadConfig = do maybeConfig <- decodeFileStrict "config.json" -- 替换成你的配置文件路径 case maybeConfig of Just config -> return config Nothing -> error "Failed to load database configuration file"
3. 编写带配置的端点处理函数
有两种简洁的方式让处理函数获取配置:
方式一:直接传递配置参数(最直观)
把配置作为参数传给处理函数,避免使用ReaderT:
-- 定义API结构和数据类型 type API = "create-item" :> ReqBody '[JSON] Item :> Post '[JSON] ItemId data Item = Item { itemName :: String } deriving (Generic, Show) instance ToJSON Item; instance FromJSON Item newtype ItemId = ItemId Int deriving (Generic, Show) instance ToJSON ItemId -- 处理函数直接接收配置参数 createItemHandler :: DBConfig -> Item -> Handler ItemId createItemHandler config item = do -- 从内存中的配置拿连接串,无需读文件 conn <- liftIO $ connectPostgreSQL (dbConnString config) -- 执行数据库插入操作 liftIO $ do [Only itemId] <- query conn "INSERT INTO items (name) VALUES (?) RETURNING id" (Only (itemName item)) close conn return (ItemId itemId)
方式二:使用ReaderT(适合复杂环境场景)
如果后续需要扩展更多环境变量,用ReaderT包裹处理函数更灵活:
import Control.Monad.Reader (ReaderT, runReaderT, ask) -- 处理函数在ReaderT DBConfig IO中运行 createItemHandler :: Item -> ReaderT DBConfig IO ItemId createItemHandler item = do config <- ask -- 从环境中获取配置 conn <- liftIO $ connectPostgreSQL (dbConnString config) liftIO $ do [Only itemId] <- query conn "INSERT INTO items (name) VALUES (?) RETURNING id" (Only (itemName item)) close conn return (ItemId itemId)
4. 绑定配置到Servant服务器
把启动时加载的配置传递给服务器,让所有端点都能复用:
api :: Proxy API api = Proxy -- 方式一对应的服务器绑定 server :: DBConfig -> Server API server config = createItemHandler config -- 方式二对应的服务器绑定(用hoistServer转换ReaderT到Handler) -- server :: DBConfig -> Server API -- server config = hoistServer api (flip runReaderT config) createItemHandler -- 应用入口 main :: IO () main = do config <- loadConfig -- 启动时仅加载一次配置 run 8080 (serve api (server config)) -- 配置全程驻留内存,所有请求共享
效果说明
- 配置仅在应用启动时读取一次,之后所有涉及数据库的请求直接从内存中获取连接串,完全避免重复读文件的开销。
- 这种实现本质上就是把
DBConfig作为单例实例在应用生命周期内共享,和Java/C#的DI单例逻辑完全一致,且没有依赖任何第三方缓存包。
内容的提问来源于stack exchange,提问作者HuntressMain
相关产品推荐
相关产品推荐

