处理变压器堆栈中的数据库访问
Dealing with database access in transformer stacks
这个问题是关于 groundhog
或 persistent
,因为我相信两者都有同样的问题。
假设我有一个转换器 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
的某种东西,因此设计可以正常工作。
这个问题是关于 groundhog
或 persistent
,因为我相信两者都有同样的问题。
假设我有一个转换器 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
的某种东西,因此设计可以正常工作。