Skip to content

Commit

Permalink
Merge pull request loco-rs#471 from loco-rs/multi-dbs
Browse files Browse the repository at this point in the history
implement multiple db's in loco extras + documentation
  • Loading branch information
kaplanelad authored Mar 5, 2024
2 parents d8ec80f + 26e6537 commit 36416e8
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 6 deletions.
127 changes: 127 additions & 0 deletions docs-site/content/docs/the-app/multiple-db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
+++
title = "Multiple Db"
description = ""
date = 2024-03-01T18:10:00+00:00
updated = 2024-03-01T18:10:00+00:00
draft = false
weight = 31
sort_by = "weight"
template = "docs/page.html"

[extra]
lead = ""
toc = true
top = false
flair =[]
+++

`Loco` enables you to work with more than one database and share instances across your application.

To set up an additional database, begin with database connections and configuration. The recommended approach is to navigate to your configuration file and add the following under [settings](@/docs/getting-started/config.md#settings):

```yaml
settings:
extra_db:
uri: postgres://loco:loco@localhost:5432/loco_app
enable_logging: false
connect_timeout: 500
idle_timeout: 500
min_connections: 1
max_connections: 1
auto_migrate: true
dangerously_truncate: false
dangerously_recreate: false
```
After configuring the database, import [loco-extras](https://crates.io/crates/loco-extras) and enable the `initializer-extra-db` feature in your Cargo.toml:
```toml
loco-extras = { version = "*", features = ["initializer-extra-db"] }
```

Next load this [initializer](@/docs/the-app/initializers.md) into `initializers` hook like this example

```rs
async fn initializers(ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
let initializers: Vec<Box<dyn Initializer>> = vec![
Box::new(loco_extras::initializers::extra_db::ExtraDbInitializer),
];
Ok(initializers)
}
```

Now, you can use the secondary database in your controller:

```rust
use sea_orm::DatabaseConnection;
use axum::{response::IntoResponse, Extension};
pub async fn list(
State(ctx): State<AppContext>,
Extension(secondary_db): Extension<DatabaseConnection>,
) -> Result<impl IntoResponse> {
let res = Entity::find().all(&secondary_db).await;
}
```

# Many Database Connections

To connect more than two different databases, load the feature `initializer-multi-db` in [loco-extras](https://crates.io/crates/loco-extras):
```toml
loco-extras = { version = "*", features = ["initializer-multi-db"] }
```

The database configuration should look like this:
```yaml
settings:
multi_db:
secondary_db:
uri: postgres://loco:loco@localhost:5432/loco_app
enable_logging: false
connect_timeout: 500
idle_timeout: 500
min_connections: 1
max_connections: 1
auto_migrate: true
dangerously_truncate: false
dangerously_recreate: false
third_db:
uri: postgres://loco:loco@localhost:5432/loco_app
enable_logging: false
connect_timeout: 500
idle_timeout: 500
min_connections: 1
max_connections: 1
auto_migrate: true
dangerously_truncate: false
dangerously_recreate: false
```

Next load this [initializer](@/docs/the-app/initializers.md) into `initializers` hook like this example

```rs
async fn initializers(ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
let initializers: Vec<Box<dyn Initializer>> = vec![
Box::new(loco_extras::initializers::multi_db::MultiDbInitializer),
];
Ok(initializers)
}
```

Now, you can use the multiple databases in your controller:

```rust
use sea_orm::DatabaseConnection;
use axum::{response::IntoResponse, Extension};
use loco_rs::db::MultiDb;
pub async fn list(
State(ctx): State<AppContext>,
Extension(multi_db): Extension<MultiDb>,
) -> Result<impl IntoResponse> {
let third_db = multi_db.get("third_db")?;
let res = Entity::find().all(third_db).await;
}
```
1 change: 1 addition & 0 deletions examples/demo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions loco-extras/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,22 @@ async-trait = { workspace = true }
axum = { workspace = true }
# initializer
axum-prometheus = { version = "0.6.1", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }

[dependencies.loco-rs]
path = "../"
version = "0.3.0"
default-features = false

default-features = true

[features]
default = []
full = ["initializer-prometheus"]
default = ["full"]
full = [
"initializer-prometheus",
"initializer-extra-db",
"initializer-multi-db",
]

initializer-prometheus = ["dep:axum-prometheus"]
initializer-extra-db = []
initializer-multi-db = ["dep:serde_json"]
30 changes: 30 additions & 0 deletions loco-extras/src/initializers/extra_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use async_trait::async_trait;
use axum::{Extension, Router as AxumRouter};
use loco_rs::{db, prelude::*};

#[allow(clippy::module_name_repetitions)]
pub struct ExtraDbInitializer;

#[async_trait]
impl Initializer for ExtraDbInitializer {
fn name(&self) -> String {
"extra-db".to_string()
}

async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
let extra_db_config = ctx
.config
.settings
.clone()
.ok_or_else(|| Error::Message("settings config not configured".to_string()))?;

let extra_db_value = extra_db_config
.get("extra_db")
.ok_or_else(|| Error::Message("settings config not configured".to_string()))?;

let extra_db = serde_json::from_value(extra_db_value.clone())?;

let db = db::connect(&extra_db).await?;
Ok(router.layer(Extension(db)))
}
}
12 changes: 12 additions & 0 deletions loco-extras/src/initializers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,17 @@
//!```rust
#![doc = include_str!("././prometheus.rs")]
//!````
//! ### Extra Database connection:
//! ```rust
#![doc = include_str!("././extra_db.rs")]
//!````
//! ### Extra Multiple Database Connections:
//! ```rust
#![doc = include_str!("././multi_db.rs")]
//!````
#[cfg(feature = "initializer-extra-db")]
pub mod extra_db;
#[cfg(feature = "initializer-multi-db")]
pub mod multi_db;
#[cfg(feature = "initializer-prometheus")]
pub mod prometheus;
28 changes: 28 additions & 0 deletions loco-extras/src/initializers/multi_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use async_trait::async_trait;
use axum::{Extension, Router as AxumRouter};
use loco_rs::{db, errors::Error, prelude::*};

#[allow(clippy::module_name_repetitions)]
pub struct MultiDbInitializer;

#[async_trait]
impl Initializer for MultiDbInitializer {
fn name(&self) -> String {
"multi-db".to_string()
}

async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
let settings = ctx
.config
.settings
.clone()
.ok_or_else(|| Error::Message("settings config not configured".to_string()))?;

let multi_db = settings
.get("multi_db")
.ok_or_else(|| Error::Message("multi_db not configured".to_string()))?;

let multi_db = db::MultiDb::new(serde_json::from_value(multi_db.clone())?).await?;
Ok(router.layer(Extension(multi_db)))
}
}
4 changes: 3 additions & 1 deletion loco-extras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//!
//! ## Features
//!
//! ### initializers
//! ### Initializers
//! * `initializer-prometheus` For adding prometheus collection metrics
//! endpoint.
//! * `initializer-extra-db` Adding extra DB connection
//! * `initializer-multi-db` Adding extra DB's connection
pub mod initializers;
37 changes: 36 additions & 1 deletion src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This module defines functions and operations related to the application's
//! database interactions.
use std::{fs::File, path::Path, time::Duration};
use std::{collections::HashMap, fs::File, path::Path, time::Duration};

use duct::cmd;
use fs_err as fs;
Expand Down Expand Up @@ -32,6 +32,41 @@ lazy_static! {
pub static ref EXTRACT_DB_NAME: Regex = Regex::new(r"/([^/]+)$").unwrap();
}

#[derive(Default, Clone)]
pub struct MultiDb {
pub db: HashMap<String, DatabaseConnection>,
}

impl MultiDb {
/// Creating multiple DB connection from the given hashmap
///
/// # Errors
///
/// When could not create database connection
pub async fn new(dbs_config: HashMap<String, config::Database>) -> AppResult<Self> {
let mut multi_db = Self::default();

for (db_name, db_config) in dbs_config {
multi_db.db.insert(db_name, connect(&db_config).await?);
}

Ok(multi_db)
}

/// Retrieves a database connection instance based on the specified key
/// name.
///
/// # Errors
///
/// Returns an [`AppResult`] indicating an error if the specified key does
/// not correspond to a database connection in the current context.
pub fn get(&self, name: &str) -> AppResult<&DatabaseConnection> {
self.db
.get(name)
.map_or_else(|| Err(Error::Message("db not found".to_owned())), Ok)
}
}

/// Verifies a user has access to data within its database
///
/// # Errors
Expand Down

0 comments on commit 36416e8

Please sign in to comment.