我试图隐藏访问Postgres数据库的实现细节。为此,我希望创建“数据库”和“事务”特征,以便在存储库结构中使用。这应允许:
具体来说,我想把tokio-postgres隐藏在这样的特性后面。我想出了这个:
use tokio_postgres::{row::Row, types::ToSql};
use async_trait::async_trait;
#[async_trait]
pub trait Database: Sync + Send {
type Transaction: Transaction;
type Connection: Connection<Transaction = Self::Transaction>;
async fn connect(&mut self) -> Result<Self::Connection, Error>;
}
#[async_trait]
pub trait Connection: Sync + Send {
type Transaction: Transaction;
async fn transaction(&mut self) -> Result<Self::Transaction, Error>;
}
#[async_trait]
pub trait Transaction: Sync + Send {
async fn query(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Vec<Row>, Error>;
async fn query_one(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Row, Error>;
async fn query_opt(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Option<Row>, Error>;
async fn commit(self) -> Result<(), Error>;
async fn rollback(self) -> Result<(), Error>;
}而且这个很管用!我对此相当自豪。我花了很长时间才弄清楚这些类型。
然而,所有这些类型参数都使得使用起来非常困难。例如,这些类型参数会影响简单存储库的类型签名:
struct Repository<D, T, C>
where
T: Transaction,
C: Connection<Transaction = T>,
D: Database<Transaction = T, Connection = C>,
{
db: D,
}
#[async_trait]
trait UserRepo {
async fn get_user(&mut self, txn: &dyn Transaction) -> Result<(), Error>;
}
#[async_trait]
impl<D, T, C> UserRepo for Repository<D, T, C>
where
T: Transaction,
C: Connection<Transaction = T>,
D: Database<Transaction = T, Connection = C>,
{
async fn get_user(&mut self, txn: &dyn Transaction) -> Result<(), Error> {
unimplemented!()
}
}Repository和UserRepo的定义是复杂的。我希望有更好的方法来做这事。Database的类型定义泄漏到Repository的定义中,后者随后泄漏到UserRepo中。
不过,用起来也不算太糟。我们可以把UserRepo的特征框起来:
struct LoginHandler {
repo: Box<dyn UserRepo>
}然而,尽管这段代码看起来很有效,但是有一个非常恼人的约束。当Transaction类型需要生存期(这需要通用关联类型)时,它就不能工作,这使得在没有装箱的情况下与tokio一起使用是不可能的。
我正在寻找任何建议,以简化这段代码,以及任何其他一般锈蚀风格的建议。
我将进一步讨论在事务中使用生命周期的要求。
tokio中的事务结构需要一生的时间。看起来是这样的:
// Type from tokio-postgres
pub struct Transaction<'a> { /* private fields */ }如果我想把它隐藏在Transaction特性后面,我会这样做:
// use tokio_postgres::Transaction as TokioTransaction
struct PostgresTransaction<'a>(TokioTransaction<'a>);
#[async_trait]
impl<'a> Transaction for PostgresTransaction<'a> {
// ...
}但是,当我试图在Connection中声明它时,它失败了:
#[async_trait]
impl Connection for PostgresConnection {
// This fails because it's different to the type signature
// in the Connection trait. This seems to require generic
// associated types.
type Transaction<'a> = PostgresTransaction<'a>;
// ...
}我甚至尝试使用#![feature(generic_associated_types)]来完成这项工作,但也遇到了一些棘手的终生问题:
#[async_trait]
pub trait Database: Sync + Send {
type Transaction<'a>: Transaction;
type Connection<'a>: Connection<Transaction<'a> = Self::Transaction<'a>>;
// ...
}这不起作用,因为Database::Connection<'a>的寿命可能不够长。使用起来也很复杂:
struct Repository<D, T, C>
where
T: Transaction,
C: for <'a> Connection<Transaction<'a> = T>,
D: for <'a> Database<Transaction<'a> = T, Connection<'a> = C>,
{
db: D,
}我想知道这里是否有使用高等级性状界的解决方案。不过,我正努力想办法解决这个问题!
希望我能够找到一种方法来简化这一点而不用使用GATs,因为我不想每晚依赖。也许用HRTBs?但在任何地方,这都会汇编:
#[async_trait]
pub trait Database: Sync + Send {
// Connection from the database connection pool.
type Connection<'a>: Connection<Transaction<'a> = Self::Transaction<'a>>
where
Self: 'a;
// A transaction whose lifetime is tied to a connection.
type Transaction<'a>: Transaction
where
Self: 'a;
async fn setup(&mut self, config: Config) -> Result<(), Error>;
async fn connect<'a>(&mut self) -> Result<Self::Connection<'a>, Error>;
}
#[async_trait]
pub trait Connection: Sync + Send {
type Transaction<'a>: Transaction
where
Self: 'a;
async fn transaction<'b>(&'b mut self) -> Result<Self::Transaction<'b>, Error>;
}
#[async_trait]
pub trait Transaction: Sync + Send {
async fn query(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Vec<Row>, Error>;
async fn query_one(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Row, Error>;
async fn query_opt(
&self,
statement: &str,
params: &[&(dyn ToSql + Sync)],
) -> Result<Option<Row>, Error>;
async fn commit(self) -> Result<(), Error>;
async fn rollback(self) -> Result<(), Error>;
}
```发布于 2022-05-31 21:56:18
对于您复杂的类型,我发现了另一种方法:
struct Repository<D: Database> {
db: D, // Note: the D type will carry its own associated types, unless you really need them you do not have to specify them
// Note: This makes the Repository struct kind of pointless, it is a direct wrapper for an object that implements Database.
// So you could just as easily work with Database directly, unless you need the level of indirection.
}
#[async_trait]
trait UserRepo {
async fn get_user(&mut self, txn: &impl Transaction) -> Result<(), Error>;
}
// Note: You can implement a trait for another trait, but this leaves the question why there is a separation in the first place.
// Unless you introduce extra restraints, or the trait you want to 'extend' is not in your control. In any other case it is
// likely more clear to just stuff the behaviour in the original trait.
#[async_trait]
impl<D: Database> UserRepo for D {
async fn get_user(&mut self, txn: &impl Transaction) -> Result<(), Error> {
unimplemented!()
}
}对于具有生存期的Transaction,我认为您需要在数据库中使用一个生命周期来连接所有其他生命周期。但这意味着接口可能会变得更难使用,因为您必须更频繁地指定生命周期。此示例(与前面代码示例中的一些代码相结合)在我的计算机上以稳定的方式编译。我希望这能帮到你!
#[async_trait]
pub trait Database<'a>: Sync + Send {
type Transaction: Transaction;
type Connection: Connection<Transaction = Self::Transaction>;
async fn connect(&mut self) -> Result<Self::Connection, Error>;
}
// Note: To use this in another struct you may need to add a marker for the compiler that it uses the lifetime.
use std::marker::PhantomData;
struct Repository<'a, D: Database<'a>> {
db: D,
phantom: PhantomData<&'a D>,
}
struct Trans<'a> {
text: &'a str,
}
#[async_trait]
impl<'a> Transaction for Trans<'a> {
...
}
struct Con<T> {
t: Vec<T>,
}
#[async_trait]
impl<T: Transaction> Connection for Con<T> {
type Transaction = T;
async fn transaction(&mut self) -> Result<Self::Transaction, Error> {
unimplemented!()
}
}
struct Db {}
// Note: this leaves this as the final database implementation.
#[async_trait]
impl<'a> Database<'a> for Db {
type Transaction = Trans<'a>;
type Connection = Con<Trans<'a>>;
async fn connect(&mut self) -> Result<Self::Connection, Error> {
unimplemented!()
}
}https://codereview.stackexchange.com/questions/276986
复制相似问题