thruster-rs / Thruster

A fast, middleware based, web framework written in Rust
MIT License
1.06k stars 47 forks source link

Application State #130

Closed inzanez closed 4 years ago

inzanez commented 4 years ago

Are there plans to allow storing application state in some form? Like an R2D2 connection manager that can be used within routes to retrieve a database session?

trezm commented 4 years ago

Great question! At the moment there aren't any plans to include that in the official API, because the general case is pretty easy to support without adding an extra surface. What has worked pretty well for me is adding a block of code that looks like this:

use lazy_static::*;
use r2d2::Pool;
use diesel::r2d2::ConnectionManager;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;

lazy_static! {
    static ref CONNECTION_POOL: Pool<ConnectionManager<PgConnection>> = {
      dotenv().ok();

      let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");

      println!("Starting db pool at: {}", database_url);
      let manager = ConnectionManager::new(database_url);
      let pool = r2d2::Pool::builder()
        .max_size(15)
        .build(manager)
        .unwrap();

      pool
    };
}

pub mod db {
  use diesel::pg::PgConnection;
  use diesel::r2d2::ConnectionManager;
  use std::env;
  use r2d2::PooledConnection;

  use crate::util::CONNECTION_POOL;

  pub fn establish_connection() -> PooledConnection<ConnectionManager<PgConnection>> {
    let database_url = env::var("DATABASE_URL")
      .expect("DATABASE_URL must be set");

    CONNECTION_POOL.get()
      .expect(&format!("Error connecting to {}", database_url))
  }
}

That being said, I'm always to hear how adding/changing API surfaces would make lives easier!

trezm commented 4 years ago

@inzanez friendly ping!

kevlarr commented 4 years ago

How would you use this lazy_static! approach if your database pool is async?

trezm commented 4 years ago

Forgive me, I'm still waking up, but do you have an example of an async database pool in which lazy_static wouldn't work? It's much easier for me to think in code!

kevlarr commented 4 years ago

Forgive me, I'm still waking up ...

Same, so this might be a little rough!

I've been experimenting with SQLx which is fully async, even establishing the connection pool.

let pool = sqlx::PgPool::new(database_url).await.unwrap();

Trying something like the following doesn't work, as the async block returns a future.

lazy_static! {
    static ref POOL: sqlx::PgPool = async {
        sqlx::PgPool::new(database_url).await.unwrap()
    };
}

My async/await game is pretty rough (and I'm not great with Rust in general), so this was a pattern I wasn't sure how to resolve.

mehcode commented 4 years ago

@kevlarr If you don't have an minimum idle set in your Pool configuration (which is the default.. so maybe we should just make Pool::new not async), you can do:

sqlx::PgPool::new(database_url).now_or_never().unwrap()

now_or_never comes from FutureExt in the futures project.

kevlarr commented 4 years ago

@mehcode Ah that's excellent - thank you!

trezm commented 4 years ago

State can now also be stored and shared at the app level thanks to #152