> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Rust : Le monorepo sans friction
- URL: https://www.sfeir.dev/front/le-monorepo-sans-friction/
- Published: 2023-10-11T09:20:40.000Z
- Updated: 2023-10-11T09:20:40.000Z
- Description: Centralisation de la connaissance, facilitation de la synchronisation des librairies et des services, standardisation de l'outillage... L'approche mono-repo offre de nombreux avantages, mais comment le mettre en place avec Rust ?
- Author: Lesage Yann
- Tags: Front, Back, Tips

## Définition d'un mono repo Rust

Un projet Rust est défini par un dossier source avec un fichier `src/main.rs`(pour une app) ou `src/lib.rs`(pour une crate) et un fichier toml `./Cargo.toml` ressemblant à :

```toml
[package]  
name = "project name"  
version = "0.1.0"  
edition = "2021"  
  
[features]  
  
[dependencies]  

```

Imaginons un monorepo qui comprend trois parties : le front, le back (avec du sql) et les types en commun. Les trois projets auront chacun leur dossier et dans le dossier root, nous auront un toml qui définira les espaces de travail. Nous obtiendrons donc l'arborescence suivante :

```
root
 |-api_types
 |  |-src
 |  |  |-lib.rs
 |  |-Cargo.toml
 |-back
 |  |-src
 |  |  |-main.rs
 |  |-Cargo.toml
 |-front
 |  |-src
 |  |  |-main.rs
 |  |-Cargo.toml
 |-Cargo.toml

```

`root/Cargo.toml`:

```toml
[workspace]  
  
members = [  
    "api_types",  
    "back",  
    "front",  
]

```

et chaque `Cargo.toml` dans les projets suivront le template normal d'un projet Rust.

## Partager du code entre les espaces de travail.

Pour poursuivre l'exemple précédent, déclarons le module qui va être partagé :

`root/api_types/Cargo.toml`:

```toml
[package]  
name = "api_types"  
version = "0.1.0"  
edition = "2021"  
  
[dependencies]  
serde = { version = "1.0", features = ["derive"] }  
serde_json = "1.0"  
sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls", "postgres", "macros" ] }  

```

Nous importons les dépendances de `serde`, `serde_json` pour les sérialisations en `JSON` et `sqlx` pour rendre la structure utilisable facilement côté back.

Les features permettent de ne prendre que le code qui nous intéresse dans une librairie. Ainsi, pour `sqlx` nous importons :

- la partie `postgresql`, qui permet la connexion à la base de données éponyme
- la partie `macro`, qui permettra de dériver les structures.
- la partie `runtime-async-std-native-tls` qui permet l'utilisation des fonctions asynchrones

Les documentations de chaque crate vous indiquent quelle feature charger pour quelle fonctionnalité.

Déclarons une structure qui pourra être utilisé dans les deux autres apps :

`root/api_types/src/lib.rs` :

```rust
use serde::{Deserialize,Serialize}; 
use sqlx;  
  
#[derive(Serialize,Deserialize, sqlx::Type)]  
#[sqlx(type_name = "container_type_enum")]
pub enum ContainerType {  
    Fish,  
    Vegetable,  
    Other,  
}

#[derive(Serialize,Deserialize)]
pub struct Container {
	pub name: String,
	pub volume: u64,
	pub content: ContainerType
}

```

Chargeons ensuite notre lib dans nos deux app :

`root/back/Cargo.toml`

```toml
[package]  
name = "back"  
version = "0.1.0"  
edition = "2021"  

[dependencies]  
api_types = { path = "../api_types"} 
serde = { version = "1.0", features = ["derive"] }  
serde_json = "1.0"  
sqlx = { version = "0.6", features = [ "postgres", "macros" ] }

```

`root/front/Cargo.toml`

```toml
[package]  
name = "front"  
version = "0.1.0"  
edition = "2021"  
  
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  
  
[dependencies]  
api_types = {path="../api_types"}  
serde = { version = "1.0", features = ["derive"] }  
serde_json = "1"

```

Et pour utiliser la structure, côté back :  
`root/src/back/src/mains.rs`

```rust
  
use api_types::container::{Container, ContainerType};

async fn main() { 
	let a = Container {name: "Aquarium".to_string(), volume: 200, content: ContainerType::Fish};
}

```

## Déclarer les features pour booster vos dépendences

Afin de réduire la taille des exécutables et le temps de compilation, nous allons déclarer des features.Ce sont des options de compilations permettant de n'embarquer que le code nécessaire à une fonctionnalité. Nous aurons ici : 

- pour le back : sérialiser avec `serde` pour envoyer au front, sérialiser et dé-sérialiser pour enregistrer dans la base (pour l'exemple d'une base sql)
- pour le front : dé-sérialiser avec `serde`

`root/api_types/src/lib.rs`

```rust
#[cfg_attr(feature = "front", derive(Deserialize))]  
#[cfg_attr(feature = "back", derive(Serialize, sqlx::Type))]  
#[cfg_attr(feature = "back", sqlx(type_name = "container_type_enum"))]  
pub enum ContainerType {  
    Fish,  
    Vegetable,  
    Other,  
}

#[cfg_attr(feature = "front", derive(Deserialize))]  
#[cfg_attr(feature = "back", derive(Serialize, sqlx::Type))]  
struct SimpleStruct {
	pub name: String,
	pub volume: u64,
	pub content: ContainerType
}

```

Dans `#[cfg_attr(feature = "front", derive(Deserialize))]` , le compilateur appliquera la macro `derive(Deserialize)` uniquement si la feature `front` est demandée. Mais il faut encore déclarer les features possibles dans le `Cargo.toml`

`root/api_types/Cargo.toml`

```toml
[package]  
name = "api_types"  
version = "0.1.0"  
edition = "2021"  

[features]    
back = ["dep:sqlx"]  
front = []
  
[dependencies]  
serde = { version = "1.0", features = ["derive"] }  
serde_json = "1.0"  
sqlx = { version = "0.6", optional = true, features = [ "runtime-async-std-native-tls", "postgres", "macros" ] }  

```

`back = ["dep:sqlx"]` signifie que pour une application demandant la feature `back`, il faudra la dépendance `sqlx` que nous venons de passer en optionnel.

Pour les utiliser nous modifierons les toml :

- du front

```toml
[dependencies]  
api_types = { path = "../api_types" features = ["front"]} 

```

- du back

```toml
[dependencies]  
api_types = { path = "../api_types" features = ["back"]} 

```

Et voici les commandes pour lancer les builds :

- `cargo build -p front`
- `cargo build -p back`

## Conclusion

Dans cet article nous aurons appris comment faire un monorepo et comment rendre modulable une crate grâce aux features. Pour aller plus loin, vous pouvez regarder comment publier une crate, packager vos applications pour différentes plateformes ou utiliser docker comme environnement unique.