In questa lezione, avendo il lato server pronto dalle lezioni precedenti, procediamo a creare il client che leggerà dati da un database MySQL e li invierà via HTTP al database MongoDB con Rust. Concluderemo poi con un test finale di verifica.
Le dipendenze
Iniziamo a predisporre le dipendenze di cui abbiamo bisogno:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "blocking", "rustls-tls"] }
mysql = "26.0"
Queste ci predisporranno tutto il necessario per eseguire una query su database MySQL e per inviare i dati raccolti alle API REST. Non troviamo pertanto alcuna traccia di librerie per MongoDB o per Warp (creazione di API REST) ma solo i crate mysql e reqwest, quest'ultimo uno dei client più conosciuti per il protocollo HTTP.
Modulo di lettura e invio dei dati con Rust
Iniziamo dal codice del modulo che si occuperà al contempo di leggere dati da MySQL e di inviarli via rete:
use serde::Serialize;
use reqwest::Client;
use mysql::*;
use mysql::prelude::*;
// struttura base dei dati
#[derive(Debug, Serialize)]
struct Libro {
titolo: String,
autore: String,
numero_pagine: i32,
}
// predisposizione della connessione
pub struct Database {
pool: Pool,
}
impl Database {
pub fn new(database_url: &str) -> Result<Self> {
let pool = Pool::new(database_url)?;
Ok(Database { pool })
}
pub async fn invio_dati(&self, api_url: &str) -> Result<()> {
let mut conn = self.pool.get_conn()?;
// estrazione dati dalla tabella
let libri: Vec<Libro> = conn.query_map(
"SELECT titolo, autore, numero_pagine FROM libri",
|(titolo, autore, numero_pagine)| {
Libro {
titolo,
autore,
numero_pagine,
}
},
)?;
// creiamo un client HTTP
let client = Client::new();
for libro in libri {
// Invio di ogni tupla all'API REST di destinazione convertita in JSON
let res = client
.post(api_url)
.json(&libro)
.send()
.await;
match res {
Ok(response) => {
println!("INVIO: {:?} con esito: {}", libro, response.status());
}
Err(e) => {
eprintln!("Errore in fase di invio. Descrizione {:?}: {}", libro, e);
}
}
}
Ok(())
}
}
Notiamo che il codice esegue essenzialmente una tipica query SQL di lettura totale dei dati SELECT titolo, autore, numero_pagine FROM libri
ed i relativi risultati vengono mappati direttamente in un elenco di elementi Libro
. Il ciclo che segue si occupa di inviare, uno alla volta, tutti i risultati estratti dal database ed il linguaggio Rust ci dà, anche in questo caso, prova della sua capacità di sequenzializzare le operazioni rendendone molto chiara la lettura:
client
.post(api_url)
.json(&libro)
.send()
.await;
Infatti, come si può vedere, viene definita un'azione di HTTP/POST verso l'indirizzo delle API destinazione il cui payload corrisponderà alla serializzazione JSON di un'istanza della struct Libro
.
Il main: attivazione della migrazione
Il codice del file main.rs
è piuttosto lineare in quanto non fa altro che attivare quanto abbiamo predisposto al paragrafo precedente avviando la migrazione:
mod mysql_database;
use mysql_database::Database;
#[tokio::main]
async fn main() {
// indirizzo del database MySQL (origine dei dati)
let database_url = "mysql://root:12345@localhost:3306/biblioteca";
// indirizzo delle API REST cui inviare i dati
let api_url = "http://localhost:3030/libri";
// lettura dei dati
let db = Database::new(database_url).expect("Connessione al database fallita");
// invio via rete
db.invio_dati(api_url).await.expect("Errore di comunicazione via rete");
}
Test finale del nostro progetto Rust
Dopo aver preparato tutto l'ambiente della sperimentazione con il codice delle ultime lezioni, possiamo provare il tutto non appena aver:
- avviato entrambi i database di cui quello MySQL dovrà disporre del database biblioteca con la tabella libri con alcuni dati di prova (abbiamo fornito allo scopo un dump in una lezione precedente). L'istanza MongoDB che riceverà i dati può invece essere completamente vuota;
- compilato entrambe le metà del progetto ovvero il codice di questa lezione e quello delle due precedenti.
Una volta avviato il server e lanciato il client otteniamo il seguente output:
INVIO: Libro { titolo: "Il mio giardino", autore: "Luca Rossi", numero_pagine: 567 } con esito: 200 OK
INVIO: Libro { titolo: "Racconti di fantasia", autore: "Selene Verdi", numero_pagine: 345 } con esito: 200 OK
INVIO: Libro { titolo: "Il grande assalto", autore: "Domenico Neri", numero_pagine: 876 } con esito: 200 OK
INVIO: Libro { titolo: "Alla scoperta del West", autore: "Giorgio Bianchi", numero_pagine: 598 } con esito: 200 OK
INVIO: Libro { titolo: "Avventure spaziali", autore: "Alessia Gialli", numero_pagine: 765 } con esito: 200 OK
che dimostra l'invio di tutti i dati presenti nella tabella MySQL.
Potremo, come reale verifica, eseguire una query nel database MongoDB e verificare che effettivamente i cinque aggregati di dati inviati siano realmente arrivati e diventati altrettanti documenti MongoDB:
> db.libri.find()
[
{
_id: ObjectId('67fbeadeb9eb08dd2d4bbc56'),
titolo: 'Il mio giardino',
autore: 'Luca Rossi',
numero_pagine: 567
},
{
_id: ObjectId('67fbeadeb9eb08dd2d4bbc57'),
titolo: 'Racconti di fantasia',
autore: 'Selene Verdi',
numero_pagine: 345
},
{
_id: ObjectId('67fbeadeb9eb08dd2d4bbc58'),
titolo: 'Il grande assalto',
autore: 'Domenico Neri',
numero_pagine: 876
},
{
_id: ObjectId('67fbeadeb9eb08dd2d4bbc59'),
titolo: 'Alla scoperta del West',
autore: 'Giorgio Bianchi',
numero_pagine: 598
},
{
_id: ObjectId('67fbeadeb9eb08dd2d4bbc5a'),
titolo: 'Avventure spaziali',
autore: 'Alessia Gialli',
numero_pagine: 765
}
]
Possiamo concludere quindi che la sperimentazione con Rust è perfettamente riuscita.