处理变压器堆栈中的数据库访问

Dealing with database access in transformer stacks

这个问题是关于 groundhogpersistent,因为我相信两者都有同样的问题。

假设我有一个转换器 Tr m a,它提供了一些功能 f :: Int -> Tr m ()。此功能需要数据库访问权限。我可以在这里使用几个选项,none 令人满意。

我可以在 Tr 内部的某处放置一个 DbPersist 变压器。实际上,我需要将它放在顶部,因为标准转换器没有 PersistBackend 个实例,而且我仍然需要为我的 Tr 新类型编写一个实例。这已经很糟糕了,因为 class 远非最小值。我还可以提升我所做的每一个数据库操作。

另一个选项是将 f 的签名更改为 PersistBackend m => Int -> Tr m ()。这将再次需要我的 Tr 新类型上的 PersistBackend 实例,或者提升。

现在才是真正的问题。我如何在已经具有 PersistBackend 约束的上下文中 运行 Tr?无法与 Tr 分享。

我可以选择第一个选项,然后 运行 Tr 内部的实际 DbPersist 转换器和一些新的连接池(据我所知,没有办法获得我已经在 PersistBackend 上下文中的池),或者我可以执行第二个选项并将 运行 函数设置为 runTr :: PersistBackend m => Tr m a -> m a。第二个选项实际上完全没问题,但这里的问题是 DbPersist,它最终必须在堆栈中的某个地方,现在在 Tr 转换器下面,并且没有 [=16] =] 构成 Tr 的标准变压器的实例。

这里正确的方法是什么?目前看来,最好的选择是在堆栈中的某个地方使用一个单独的 ReaderT,根据请求为我提供连接池,然后在我想要访问的任何地方对该池进行 runDbConn数据库。看到 DbPersist 基本上已经只是一个 ReaderT 我不明白必须这样做的意义。

土拨鼠

我建议使用 master 分支中的 latest groundhog。尽管我即将描述的更改似乎已在 2015 年 9 月实施,但 Hackage 尚未发布任何版本。但作者似乎已经解决了这个问题。

提示,PersistBackend 现在更易于实施 class,与曾经的数十种方法相比大大减少:

class (Monad m, Applicative m, Functor m, MonadIO m, ConnectionManager (Conn m), PersistBackendConn (Conn m)) => PersistBackend m where
  type Conn m
  getConnection :: m (Conn m)

instance (Monad m, Applicative m, Functor m, MonadIO m, PersistBackendConn conn) => PersistBackend (ReaderT conn m) where
  type Conn (ReaderT conn m) = conn
  getConnection = ask

他们为 ReaderT conn m 编写了一个实例(DbPersist 已被弃用并别名为 ReaderT conn),如果您选择,您也可以轻松地为 Tr (ReaderT conn) 编写一个实例走把 ReaderT 放在里面而不是外面的路线。它不完全是一个 mtl monad 转换器,因为你必须实例化 Tr m 而不是 Tr,但是这个和他们使用的相关数据类型技巧应该允许你使用自定义 monad堆栈没有太多大惊小怪。

您选择的任何一个选项都可能需要一些提升。在我个人看来,我会把 ReaderT conn 贴在堆栈的最外面。这样,mtl 助手仍然可以举起你的大部分堆叠,你可以粘上额外的举升机将其带回家。而且,如果你坚持使用 Hackage 上的版本,这似乎是唯一合理的选择,否则你将拥有(旧的)整体 PersistBackend class.

坚持不懈

Persistent 更直接一点:只要 monad 转换器堆栈包含 ReaderT SqlBackend 并终止于 IO,您就可以解除对 runSqlPool :: MonadBaseControlIO m => ReaderT SqlBackend m a -> Pool SqlBackend -> m a 的调用。所有持久性操作都被定义为 return 类型 ReaderT backend m a 的某种东西,因此设计可以正常工作。