DEV Community

Werner Echezuría
Werner Echezuría

Posted on • Edited on

Practical Rust Web Development - Connection Pool

In a previous post we create the basics Api for a web store. This time I'll be covering an enhancement to the database connection, well be using a generic connection pool known as r2d2, the main advantage is that we can improve the connection to the database by caching the resource, Diesel provides a module for that, we just need to enable it.

Cargo.toml:

diesel = { version = "1.0.0", features = ["postgres", "r2d2"] }

Then we need to configure our db to handle pools connections, we add a new function init_pool to instantiate a new db pool, we edit our db_connection.rs file:

src/db_connection.rs:

// This is my diff git showing what the changes are.

-use diesel::prelude::*;
 use diesel::pg::PgConnection;
 use dotenv::dotenv;
 use std::env;
+use diesel::r2d2::{ Pool, PooledConnection, ConnectionManager, PoolError };

-pub fn establish_connection() -> PgConnection {
+pub type PgPool = Pool<ConnectionManager<PgConnection>>;
+pub type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
+
+fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
+    let manager = ConnectionManager::<PgConnection>::new(database_url);
+    Pool::builder().build(manager)
+}
+
+pub fn establish_connection() -> PgPool {
     dotenv().ok();

     let database_url = env::var("DATABASE_URL")
         .expect("DATABASE_URL must be set");
-    PgConnection::establish(&database_url)
-        .expect(&format!("Error connecting to {}", database_url))
+    init_pool(&database_url).expect("Failed to create pool")
 }

We need to perform other changes, I'll paste my diff git as well, you can see what lines have to be remove or change.

src/models/product.rs:

+use diesel::PgConnection;

 impl ProductList {
-    pub fn list() -> Self {
+    pub fn list(connection: &PgConnection) -> Self {
         use diesel::RunQueryDsl;
         use diesel::QueryDsl;
         use crate::schema::products::dsl::*;
-        use crate::db_connection::establish_connection;
-
-        let connection = establish_connection();

         let result = 
             products
                 .limit(10)
-                .load::<Product>(&connection)
+                .load::<Product>(connection)
                 .expect("Error loading products");

         ProductList(result)
...

 impl NewProduct {
-    pub fn create(&self) -> Result<Product, diesel::result::Error> {
+    pub fn create(&self, connection: &PgConnection) -> Result<Product, diesel::result::Error> {
         use diesel::RunQueryDsl;
-        use crate::db_connection::establish_connection;

-        let connection = establish_connection();
         diesel::insert_into(products::table)
             .values(self)
-            .get_result(&connection)
+            .get_result(connection)
     }
 }
...

 impl Product {
-    pub fn find(id: &i32) -> Result<Product, diesel::result::Error> {
+    pub fn find(id: &i32, connection: &PgConnection) -> Result<Product, diesel::result::Error> {
         use diesel::QueryDsl;
         use diesel::RunQueryDsl;
-        use crate::db_connection::establish_connection;
-
-        let connection = establish_connection();

-        products::table.find(id).first(&connection)
+        products::table.find(id).first(connection)
     }

-    pub fn destroy(id: &i32) -> Result<(), diesel::result::Error> {
+    pub fn destroy(id: &i32, connection: &PgConnection) -> Result<(), diesel::result::Error> {
         use diesel::QueryDsl;
         use diesel::RunQueryDsl;
         use crate::schema::products::dsl;
-        use crate::db_connection::establish_connection;

-        let connection = establish_connection();
-
-        diesel::delete(dsl::products.find(id)).execute(&connection)?;
+        diesel::delete(dsl::products.find(id)).execute(connection)?;
         Ok(())
     }

-    pub fn update(id: &i32, new_product: &NewProduct) -> Result<(), diesel::result::Error> {
+    pub fn update(id: &i32, new_product: &NewProduct, connection: &PgConnection) ->
+     Result<(), diesel::result::Error> {
         use diesel::QueryDsl;
         use diesel::RunQueryDsl;
         use crate::schema::products::dsl;
-        use crate::db_connection::establish_connection;
-
-        let connection = establish_connection();

         diesel::update(dsl::products.find(id))
             .set(new_product)
-            .execute(&connection)?;
+            .execute(connection)?;
         Ok(())
     }
 }

In models/product.rs We just add a new parameter to every method to pass the database connection.

src/handlers/products.rs:

 use actix_web::{HttpRequest, HttpResponse };
+use actix_web::web;

 use crate::models::product::ProductList;
+use crate::db_connection::{ PgPool, PgPooledConnection };
+
+fn pg_pool_handler(pool: web::Data<PgPool>) -> Result<PgPooledConnection, HttpResponse> {
+    pool
+    .get()
+    .map_err(|e| {
+        HttpResponse::InternalServerError().json(e.to_string())
+    })
+}

-pub fn index(_req: HttpRequest) -> HttpResponse {
-    HttpResponse::Ok().json(ProductList::list())
+pub fn index(_req: HttpRequest, pool: web::Data<PgPool>) -> Result<HttpResponse, HttpResponse> {
+    let pg_pool = pg_pool_handler(pool)?;
+    Ok(HttpResponse::Ok().json(ProductList::list(&pg_pool)))
 }

 use crate::models::product::NewProduct;
-use actix_web::web;

-pub fn create(new_product: web::Json<NewProduct>) -> Result<HttpResponse, HttpResponse> {
+pub fn create(new_product: web::Json<NewProduct>, pool: web::Data<PgPool>) -> Result<HttpResponse, HttpResponse> {
+    let pg_pool = pg_pool_handler(pool)?;
     new_product
-        .create()
+        .create(&pg_pool)
         .map(|product| HttpResponse::Ok().json(product))
         .map_err(|e| {
             HttpResponse::InternalServerError().json(e.to_string())

-pub fn show(id: web::Path<i32>) -> Result<HttpResponse, HttpResponse> {
-    Product::find(&id)
+pub fn show(id: web::Path<i32>, pool: web::Data<PgPool>) -> Result<HttpResponse, HttpResponse> {
+    let pg_pool = pg_pool_handler(pool)?;
+    Product::find(&id, &pg_pool)
         .map(|product| HttpResponse::Ok().json(product))
         .map_err(|e| {
             HttpResponse::InternalServerError().json(e.to_string())
         })
 }

-pub fn destroy(id: web::Path<i32>) -> Result<HttpResponse, HttpResponse> {
-    Product::destroy(&id)
+pub fn destroy(id: web::Path<i32>, pool: web::Data<PgPool>) -> Result<HttpResponse, HttpResponse> {
+    let pg_pool = pg_pool_handler(pool)?;
+    Product::destroy(&id, &pg_pool)
         .map(|_| HttpResponse::Ok().json(()))
         .map_err(|e| {
             HttpResponse::InternalServerError().json(e.to_string())
         })
 }

-pub fn update(id: web::Path<i32>, new_product: web::Json<NewProduct>) -> Result<HttpResponse, HttpResponse> {
-    Product::update(&id, &new_product)
+pub fn update(id: web::Path<i32>, new_product: web::Json<NewProduct>, pool: web::Data<PgPool>) -> Result<HttpResponse, HttpResponse> {
+    let pg_pool = pg_pool_handler(pool)?;
+    Product::update(&id, &new_product, &pg_pool)
         .map(|_| HttpResponse::Ok().json(()))
         .map_err(|e| {
             HttpResponse::InternalServerError().json(e.to_string())

In handlers/products.rs we add a new function to send the request parameter and return the db pool we need for our model, the source code with the new changes is available here.

Top comments (6)

Collapse
 
jtr109 profile image
Ryan Li

Hi, your series is great! It helps me a lot about Rust learning.

A change in main.rs is not mentioned in this article. This issue causes the project built well but respond App data is not configured, to configure use App::data() for all requests.

A tip about it may help newbies like me.

Collapse
 
werner profile image
Werner Echezuría

Hi, yeah, you're right, that's why I always suggest to take a look at the source code in the end of every article, I'm sure I forgot a lot of others details.

Collapse
 
jtr109 profile image
Ryan Li

Great suggestion. That is what I did. ❤️

Collapse
 
issacnguyen_56 profile image
Issac-Nguyen

Hi, your series is great! I can learn many thing about Rust
But in the series 2, I got this error in firefox console, and I don't see any product when I route to /products

"The character encoding of the plain text document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the file needs to be declared in the transfer protocol or file needs to use a byte order mark as an encoding signature."

Collapse
 
werner profile image
Werner Echezuría

Hi, I'm glad you like them.

About your problem, did you check master?, sometimes I fix some things and forgot to update the tags.

Collapse
 
issacnguyen_56 profile image
Issac-Nguyen

Hi, I get the code on master and run it, but the problem still happen. I'm using ubuntu 18.04.