You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 08:40:27