Come distribuire un’applicazione Rust?
Rust è stato il linguaggio più apprezzato nel sondaggio degli sviluppatori di StackOverflow per più di 4 anni, grazie alle molteplici caratteristiche che offre agli utenti.
Mozilla ha creato Rust per essere affidabile, performante e facile da sviluppare. Rust ha una sintassi simile a quella di linguaggi come C++ e C, i cui sviluppatori sono il target principale del linguaggio.
Rust si concentra anche sulla sicurezza della memoria e sulla concorrenza, con modelli che evitano le insidie associate che gli sviluppatori che utilizzano altri linguaggi devono affrontare.
Imparerete a costruire API in Rust per sfruttare i vantaggi di questo articolo. Imparerete a creare, containerizzare e distribuire un’applicazione Rust sul servizio di containerizzazione gratuito di Back4app.
Contents
- 0.1 Vantaggi dell’utilizzo della Rust
- 0.2 Limitazioni dell’uso di Rust
- 0.3 Opzioni di distribuzione di Rust
- 0.4 Il processo di distribuzione dell’applicazione Rust
- 1 Che cos’è Back4app?
- 1.1 Costruzione e distribuzione
- 1.2 La funzione del gestore POST
- 1.3 La funzione GET Handler
- 1.4 La funzione PUT Handler
- 1.5 La funzione del gestore DELETE
- 1.6 Mappatura delle funzioni del gestore sulle rotte
- 1.7 Containerizzazione di applicazioni Rust con Docker
- 1.8 Distribuzione di un contenitore su Back4app
- 1.9 Distribuzione con l’agente AI di Back4app
- 1.10 Conclusione
Vantaggi dell’utilizzo della Rust
L’uso di Rust nei vostri progetti offre numerosi vantaggi. Eccone alcuni importanti:
Astrazioni a costo zero
Rust fornisce astrazioni di alto livello senza imporre costi aggiuntivi di runtime. Ciò significa che le astrazioni utilizzate nel codice (funzioni, iteratori o generici) non rendono i programmi più lenti.
Il compilatore Rust ottimizza manualmente le astrazioni per il codice di basso livello compilato. Rust colma il divario tra il controllo espressivo, di basso livello e a grana fine sulle prestazioni.
L’approccio Fearless Concurrency per la sicurezza della memoria nei programmi concorrenti
Rust adotta un “approccio senza paura” alla concorrenza, caratterizzato da sicurezza ed efficienza. Il modello di concorrenza di Rust sfrutta il modello di proprietà e il controllo dei tipi per prevenire le corse dei dati in fase di compilazione.
Questa funzione consente di scrivere applicazioni multi-thread senza gli inconvenienti della concorrenza a stati condivisi, come deadlock e condizioni di gara.
Sistema di tipi avanzato e modello di proprietà
Il sistema di tipi di Rust e le sue regole di proprietà sono caratteristiche uniche che aiutano a garantire la sicurezza della memoria.
Il modello di proprietà utilizza il borrow checker per garantire che ogni dato abbia un unico proprietario e gestisce il suo ciclo di vita per evitare problemi come dangling pointer e memory leak.
Compatibilità e integrazione multipiattaforma
Rust è un’ottima scelta se volete creare applicazioni multipiattaforma. È possibile scrivere il codice una sola volta e compilarlo su più piattaforme senza apportare modifiche significative alla base di codice esistente.
Rust si integra bene con altri linguaggi di programmazione, in particolare con il C, rendendo il linguaggio adatto all’assemblaggio web e alle attività dei sistemi embedded.
Limitazioni dell’uso di Rust
È inevitabile che si verifichino degli inconvenienti durante la creazione di applicazioni di livello produttivo con Rust.
Alcuni di questi possono includere la curva di apprendimento ripida di Rust, il tempo di compilazione più lungo dovuto ai controlli della memoria e di altro tipo e il suo nuovo e piccolo ecosistema.
Ci sono alcuni inconvenienti che si possono incontrare quando si costruiscono prodotti di livello produttivo con Rust. Eccone alcuni:
La curva di apprendimento per la programmazione di Rust è ripida
Rispetto ad altri linguaggi popolari (Go, Python, JavaScript, ecc.), occorre molto tempo per padroneggiare Rust e creare applicazioni di livello produttivo con questo linguaggio.
Questo non dovrebbe farvi desistere dall’utilizzare Rust. Se si impara a padroneggiare Rust, si diventa molto produttivi costruendo e distribuendo applicazioni e si ottengono tutti i vantaggi dell’uso di Rust.
I programmi Rust hanno tempi di compilazione lunghi
I controlli sulla memoria e sulla concorrenza in fase di compilazione, insieme a diversi altri fattori, determinano tempi di compilazione lunghi per i programmi Rust.
A seconda delle dimensioni dell’applicazione, i lunghi tempi di compilazione possono causare colli di bottiglia nelle fasi di sviluppo o di produzione.
Rust ha un ecosistema di biblioteche più piccolo
Rust è relativamente nuovo rispetto a molti altri linguaggi popolari e l’ecosistema di librerie utilizzabili è limitato.
Molte librerie (crates) sono ancora in fase di produzione e si possono consultare siti web come AreWeWebYet per avere una panoramica dei crates pronti per la produzione che si possono usare per costruire applicazioni web in Rust.
Opzioni di distribuzione di Rust
Rust sta già ottenendo un’ampia adozione, quindi ci sono molte opzioni di distribuzione che potete scegliere per le vostre applicazioni.
La maggior parte delle opzioni di distribuzione di Rust sono piattaforme basate su IaaS o CaaS. È possibile sceglierne una in base alle specifiche del progetto.
Infrastruttura come servizio (IaaS) come AWS
I fornitori di Infrastructure as a Service (IaaS) forniscono l’infrastruttura per la distribuzione e la gestione delle applicazioni in esecuzione su macchine virtuali nel cloud.
È possibile utilizzare i servizi forniti dalle piattaforme IaaS per distribuire le applicazioni Rust su macchine virtuali che eseguono sistemi operativi come Linux, Windows, macOS e altri sistemi operativi supportati da Rust.
Ecco un elenco delle piattaforme IaaS più diffuse:
- Amazon Web Services
- Digital Ocean
- Google Cloud
- Linode
- Microsoft Azure
La containerizzazione come servizio come Back4app Containers
I fornitori di servizi di containerizzazione (CaaS) vi aiutano a facilitare la distribuzione delle vostre applicazioni con le tecnologie di containerizzazione.
Per distribuire la vostra applicazione su piattaforme che supportano la containerizzazione, dovrete raggruppare la vostra applicazione e tutte le sue dipendenze in un contenitore isolato.
I container sono isolati e portatili, ma dovrete lavorare entro i confini delle caratteristiche del fornitore CaaS.
Alcuni fornitori di IaaS forniscono funzioni CaaS. Inoltre, esistono piattaforme che forniscono solo funzionalità CaaS flessibili e isolate.
Ecco un elenco di alcune piattaforme CaaS:
- Oracle Container Service
- Back4app
- Mirantix
- Docker Enterprise
Il processo di distribuzione dell’applicazione Rust
In questa sezione scoprirete come distribuire la vostra applicazione Rust sulla piattaforma CaaS di Back4app.
Che cos’è Back4app?
Back4app è una piattaforma cloud che potete sfruttare per creare e distribuire tutti i tipi di servizi di backend per le vostre applicazioni mobili, web e di altro tipo.
Back4app fornisce un agente AI che può essere utilizzato per semplificare la distribuzione delle app sulla piattaforma. È possibile utilizzarlo per gestire i repository GitHub, distribuire facilmente il codice sul cloud e gestire le app in esecuzione.
Sui server backend di Back4app è possibile distribuire ed eseguire container personalizzati tramite la funzionalità CaaS.
Utilizzando le immagini del contenitore, è possibile estendere la logica dell’applicazione senza preoccuparsi di mantenere l’architettura del server.
Costruzione e distribuzione
Per seguire questa esercitazione è necessario che Rust sia installato sul computer. Potete visitare la pagina delle installazioni di Rust per conoscere le diverse opzioni di installazione disponibili.
Una volta installato Rust, creare una sessione di terminale ed eseguire questo comando per inizializzare un nuovo progetto Rust
mkdir back4app-rust-deployment && cd back4app-rust-deployment && cargo init
Quando si esegue il comando, si dovrebbe vedere un file cargo.toml
nella nuova cartella appena creata. Il file cargo.toml
verrà utilizzato per gestire le dipendenze.
Quindi, aggiungere queste direttive alla sezione [dependencies]
del file Cargo.toml
, per installare queste dipendenze quando si costruisce l’applicazione.
[dependencies]
actix-web = "4.0"
serde = { version = "1.0", features = ["derive"] }
serde_derive = { version = "1.0" }
serde_json = "1.0"
lazy_static = "1.4"
Il crate actix-web
fornisce le funzioni di routing e altre funzioni legate all’HTTP, i crate serde
, serde_derive
e serde_json
forniscono le funzioni per le diverse operazioni JSON e il crate lazy_static
fornisce la memorizzazione dei dati in memoria per l’API in runtime.
Aggiungere queste importazioni all’inizio del file [main.rs]():
use serde::{Serialize, Deserialize};
use actix_web::{web, App, HttpServer, HttpResponse, Error};
use std::sync::Mutex;
extern crate lazy_static;
use lazy_static::lazy_static;
Si può usare una struct per definire la struttura dei dati dell’API in base ai campi necessari. Ecco una struttura che rappresenta una persona con un ID, un nome utente e un’e-mail.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Person {
pub id: i32,
pub username: String,
pub email: String,
}
Le #[derive(Serialize, Deserialize, Debug, Clone)]
sono implementazioni del crate serde_derive
per la struttura Person
, per accedere e utilizzare le sue funzioni.
Ecco come si può usare il crate lazy_static
per impostare un archivio di dati in-memory per la propria API, basato sul tipo di struct Person
:
lazy_static! {
static ref DATA_STORE: Mutex<Vec<Person>> = Mutex::new(Vec::new());
}
È stata creata una memoria condivisa, inizializzata in modo pigro e a prova di thread, per la struttura People. La si utilizzerà nelle funzioni di gestione per memorizzare e recuperare i dati.
La funzione del gestore POST
La funzione di gestione della richiesta POST accetta come input una rappresentazione JSON della struttura Persona
. Restituisce quindi una risposta HTTP e un errore al client su richiesta.
Aggiungere questo blocco di codice al file [main.rs]()
per implementare la funzionalità di gestione delle richieste POST.
async fn create_person(new_person: web::Json<Person>) -> Result<HttpResponse, Error> {
let mut data_store = DATA_STORE.lock().unwrap();
let new_id = data_store.len() as i32 + 1;
let mut person = new_person.into_inner();
person.id = new_id;
data_store.push(person.clone());
Ok(HttpResponse::Ok().json(person))
}
La funzione create_person
è una funzione asincrona che accede all’archivio dati condiviso, genera un nuovo ID per la struttura Person
, converte la rappresentazione JSON di Person
in una struttura e la inserisce nell’archivio dati.
A seguito di una richiesta andata a buon fine, la funzione fornisce al client i dati immessi nel database insieme a un codice di stato 200.
La funzione GET Handler
Qui una funzione GET handler dimostra di leggere tutti i dati presenti nell’archivio dati e di restituirli al client come JSON.
Aggiungere questo blocco di codice al progetto per implementare una funzione GET handler
async fn get_people() -> Result<HttpResponse, Error> {
let data_store = DATA_STORE.lock().unwrap();
let people: Vec<Person> = data_store.clone();
Ok(HttpResponse::Ok().json(people))
}
La funzione get_people
è una funzione asincrona che accede all’archivio dati e scrive il contenuto al client come risposta.
Se la richiesta ha successo, la funzione risponde con il codice di stato 200
al client con tutti i dati presenti nell’archivio dati.
La funzione PUT Handler
La funzione di gestione della richiesta PUT deve aggiornare una voce dell’archivio dati in base a un campo dell’oggetto.
Ecco come si può implementare una funzione di gestione PUT per la propria API:
async fn update_person(
id: web::Path<i32>,
person_update: web::Json<Person>,
) -> Result<HttpResponse, Error> {
let mut data_store = DATA_STORE.lock().unwrap();
if let Some(person) = data_store.iter_mut().find(|p| p.id == *id) {
*person = person_update.into_inner();
Ok(HttpResponse::Ok().json("Person updated successfully"))
} else {
Ok(HttpResponse::NotFound().json("Person not found"))
}
}
La funzione update_person
riceve l’ID e la nuova voce dalla richiesta. Quindi attraversa l’archivio dati e sostituisce la voce con quella nuova, se esiste.
La funzione del gestore DELETE
La funzione di richiesta DELETE accetta un argomento: il campo ID della richiesta effettuata. All’esecuzione della funzione, cancellerà la voce con l’ID dall’archivio dati.
Aggiungete al vostro programma questa implementazione della funzione di gestione DELETE.
// DELETE
pub async fn delete_person(id: web::Path<i32>) -> Result<HttpResponse, Error> {
let mut data_store = DATA_STORE.lock().unwrap();
if let Some(index) = data_store.iter().position(|p| p.id == *id) {
data_store.remove(index);
Ok(HttpResponse::Ok().json("Deleted successfully"))
} else {
Ok(HttpResponse::NotFound().json("Person not found"))
}
}
La funzione delete_person
elimina dall’archivio dati la voce con l’ID specificato. A seconda dello stato dell’operazione, la funzione restituisce al client una stringa e un codice di stato.
Mappatura delle funzioni del gestore sulle rotte
Dopo aver definito gli endpoint, è necessario mappare le rotte alle funzioni del gestore per accedere alle funzionalità di queste ultime.
Ecco come assegnare le rotte alle funzioni del gestore:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/person", web::post().to(create_person))
.route("/people", web::get().to(get_people))
.route("/person/{id}", web::put().to(update_person))
.route("/person/{id}", web::delete().to(delete_person))
})
.bind("0.0.0.0:8000")?
.run()
.await
}
La funzione main
è una funzione asincrona che configura il server dopo aver mappato le rotte alle funzioni del gestore.
La funzione HttpServer::new
istanzia un server HTTP, la funzione App::new()
crea una nuova istanza di applicazione e la funzione route
mappa le rotte alla funzione gestore.
La funzione bind
specifica l’indirizzo della nuova applicazione e la funzione run
esegue l’applicazione.
Containerizzazione di applicazioni Rust con Docker
Docker è la tecnologia di containerizzazione più diffusa sul mercato. È possibile containerizzare le applicazioni Rust con Docker per la portabilità e distribuirle su Back4app con pochi clic.
Eseguire questo comando per creare un nuovo file Docker nel progetto:
touch Dockerfile
Aprite il file Docker e aggiungete queste istruzioni di compilazione al file Docker:
# Use Rust Nightly as the base image
FROM rustlang/rust:nightly
# Set the working directory inside the container
WORKDIR /usr/src/myapp
# Copy the current directory contents into the container
COPY . .
# Build the application
RUN cargo build --release
# Expose port 8000
EXPOSE 8000
# Define the command to run the application
CMD ["./target/release/back4app-rust-deployment"]
Queste istruzioni specificano l’immagine di base e le istruzioni di compilazione per la containerizzazione dell’applicazione Rust con Docker.
Ecco la ripartizione del contenuto del file Docker:
- La direttiva
FROM rustlang/rust:nightly
specifica l’immagine di base per il file Docker. Docker estrae questa immagine dal repository e costruisce i programmi su di essa. - La direttiva
WORKDIR /usr/src/myapp
imposta la cartella di lavoro dell’applicazione all’interno del contenitore. - La direttiva
COPY . .
copia tutto il contenuto della propria directory di lavoro nella directory di lavoro corrente del contenitore. - La direttiva
RUN cargo build --release
esegue il comando per costruire l’applicazione nel contenitore. - La direttiva
EXPOSE 8000
espone la porta8000
del contenitore per le richieste in arrivo. - Il
CMD ["./target/release/back4app-rust-deployment"]
esegue il programma (l’eseguibile dell’operazione di compilazione).
Una volta scritto il file Docker, si può procedere al deploy del container sul servizio container di Back4app.
Distribuzione di un contenitore su Back4app
Per distribuire i container è necessario creare un account su Back4app.
Ecco i passaggi per creare un account Back4app.
- Visita il sito web di Back4app
- Fare clic sul pulsante Iscriviti nell’angolo in alto a destra della pagina.
- Completare il modulo di iscrizione e inviarlo per creare l’account.
Dopo aver creato con successo un account Back4app, accedere e fare clic sul pulsante NUOVA APP
situato nell’angolo in alto a destra della pagina di destinazione.
Vi verranno presentate delle opzioni per scegliere come costruire la vostra applicazione. Scegliere l’opzione Container as a Service
.
Ora, collegate il vostro account Github al vostro account Back4app e configurate l’accesso ai repository del vostro account o a un progetto specifico.
Scegliete l’applicazione che volete distribuire (quella di questa esercitazione) e fate clic su Seleziona.
Facendo clic su select, si accede a una pagina in cui è possibile inserire informazioni sulla propria applicazione, tra cui il nome del ramo, la directory principale e le variabili d’ambiente.
Il processo di distribuzione si avvia automaticamente,
Distribuzione con l’agente AI di Back4app
Per potenziare il vostro flusso di lavoro di sviluppo, potete anche distribuire la vostra applicazione utilizzando l’agente AI di Back4app, come potete vedere nell’immagine qui sotto:
Seguite questo link per installare l’applicazione contenitore Back4app nel vostro account GitHub e seguite i passaggi dell’immagine qui sopra per configurarla.
Una volta completata la configurazione dell’app, si può procedere alla distribuzione dell’applicazione con l’agente AI.
Seguite il link fornito per monitorare l’avanzamento della distribuzione della vostra applicazione.
Conclusione
Avete imparato a costruire e distribuire un’applicazione Rust containerizzata con Docker su Back4app.
Il deploy delle applicazioni su Back4app è un ottimo modo per semplificare la gestione dell’infrastruttura di backend.
Back4App fornisce strumenti potenti per gestire i dati, scalare l’applicazione e monitorarne le prestazioni.
È una scelta eccellente per gli sviluppatori che desiderano creare grandi applicazioni piuttosto che gestire server.