Le fichier module.rs avait deux problèmes :
OrderModule suggérait un service gérant uniquement des Orders// ❌ Nom trompeur
pub struct OrderModule; // Mais gère Order + Invoice + Payment !
impl Module for OrderModule {
fn name(&self) -> &str {
"order-service" // ❌ Nom trompeur
}
fn links_config(&self) -> Result<LinksConfig> {
LinksConfig::from_yaml_str(r#"
// ❌ 60+ lignes de YAML hardcodé dans le code
"#)
}
}
Problèmes :
OrderModule est trompeur → Ce service gère Order, Invoice ET Paymentmicroservice/
├── config/ # 🆕 Configuration externalisée
│ └── links.yaml # Configuration YAML séparée
├── module.rs # Module Rust (ne contient que le code)
├── main.rs
└── entities/
├── order/
├── invoice/
└── payment/
examples/microservice/config/
└── links.yaml # Configuration complète des entités et liens
config/links.yaml# Configuration for the billing microservice
# This microservice manages orders, invoices, and payments
entities:
- singular: order
plural: orders
auth:
list: authenticated
get: authenticated
# ...
- singular: invoice
plural: invoices
# ...
- singular: payment
plural: payments
# ...
links:
- link_type: has_invoice
source_type: order
target_type: invoice
# ...
validation_rules:
has_invoice:
- source: order
targets: [invoice]
- /// OrderModule implementing the Module trait
- pub struct OrderModule;
+ /// Billing microservice module
+ ///
+ /// Handles the complete billing workflow:
+ /// - Orders: Customer orders
+ /// - Invoices: Billing documents generated from orders
+ /// - Payments: Payment transactions for invoices
+ pub struct BillingModule;
- impl Module for OrderModule {
+ impl Module for BillingModule {
fn name(&self) -> &str {
- "order-service"
+ "billing-service"
}
fn links_config(&self) -> Result<LinksConfig> {
- LinksConfig::from_yaml_str(r#"
- // 60+ lignes de YAML...
- "#)
+ let config_path = concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/examples/microservice/config/links.yaml"
+ );
+ LinksConfig::from_yaml_file(config_path)
}
}
- use module::OrderModule;
+ use module::BillingModule;
fn main() {
- let module = OrderModule;
+ let module = BillingModule;
let config = Arc::new(module.links_config()?);
- println!("🚀 Starting {} v{}", module.name(), module.version());
+ // Affiche maintenant: "🚀 Starting billing-service v1.0.0"
}
❌ OrderModule
→ Suggère : Service gérant uniquement des Orders
→ Réalité : Gère Orders, Invoices, Payments
✅ BillingModule (billing-service)
→ Clair : Service de facturation complet
→ Cohérent : Order → Invoice → Payment (workflow de facturation)
| Aspect | Avant | Après |
|---|---|---|
| Config | Dans module.rs (mélangé) | Dans config/links.yaml (séparé) |
| Lignes Rust | ~90 lignes | ~35 lignes (-60%) |
| Édition config | Modifier code Rust + recompiler | Éditer YAML (pas de recompilation) |
| Lisibilité | Config noyée dans le code | Config visible et éditable |
Avant (❌):
Modifier config → Toucher module.rs → Recompiler → Redéployer
Après (✅):
Modifier config/links.yaml → Redémarrer service (pas de recompilation)
// module.rs : Code Rust pur
pub struct BillingModule;
impl Module for BillingModule {
// Logique métier
}
// config/links.yaml : Configuration pure
entities:
- singular: order
# Configuration déclarative
Ajouter des configs environnement-spécifiques :
config/
├── links.yaml # Base
├── links.dev.yaml # Dev overrides
├── links.staging.yaml # Staging overrides
└── links.prod.yaml # Production overrides
microservice/
├── module.rs (90 lignes: code + config mélangés)
│ ├── OrderModule struct (trompeur)
│ └── Config YAML hardcodée (60 lignes)
├── main.rs
└── entities/
Problèmes :
OrderModule pour 3 entités)microservice/
├── config/
│ └── links.yaml (60 lignes: config pure)
├── module.rs (35 lignes: code pur)
│ └── BillingModule struct (clair)
├── main.rs
└── entities/
Avantages :
BillingModule = workflow complet)La configuration est de la donnée, pas du code.
Avant : Config = code Rust (String litérale)
LinksConfig::from_yaml_str(r#"..."#) // ❌ Config dans le code
Après : Config = fichier externe
LinksConfig::from_yaml_file("config/links.yaml") // ✅ Config séparée
Avant : module.rs avait 2 responsabilités
Après : Responsabilités séparées
module.rs → Structure du module (code)config/links.yaml → Configuration (data)Avant :
1. Ouvrir module.rs
2. Trouver la config dans 90 lignes de code
3. Modifier le YAML hardcodé
4. cargo build # Recompilation obligatoire
5. Redéployer
Après :
1. Ouvrir config/links.yaml
2. Modifier directement
3. Redémarrer le service # Pas de recompilation !
Avant :
1. Modifier module.rs (code Rust)
2. Ajuster l'indentation YAML dans la string
3. cargo build
4. Tester
Après :
1. Éditer config/links.yaml (syntaxe YAML native)
2. Validation par l'IDE (si plugin YAML)
3. Redémarrer
Avant :
// ❌ Impossible sans modifier le code
#[cfg(feature = "dev")]
const CONFIG: &str = r#"..."#;
#[cfg(feature = "prod")]
const CONFIG: &str = r#"..."#;
Après :
// ✅ Simple variable d'environnement
let env = std::env::var("ENV").unwrap_or("dev".to_string());
let config_path = format!("config/links.{}.yaml", env);
LinksConfig::from_yaml_file(&config_path)
microservice/
├── config/
│ └── links.yaml # Configuration unique
├── module.rs # Module trait
├── main.rs
└── entities/
microservice/
├── config/
│ ├── links.yaml # Base (commun)
│ ├── links.dev.yaml # Dev overrides
│ ├── links.staging.yaml
│ └── links.prod.yaml # Production
├── module.rs
├── main.rs
└── entities/
microservice/
├── config/
│ ├── links.yaml # Entités et liens
│ ├── auth.yaml # Policies d'autorisation
│ ├── db.yaml # Configuration base de données
│ └── server.yaml # Configuration serveur
├── module.rs
├── main.rs
└── entities/
$ cargo build --example microservice
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.90s
$ cargo run --example microservice
🚀 Starting billing-service v1.0.0 # ✅ Nouveau nom
📦 Entities: ["order", "invoice", "payment"]
📝 Creating test data:
Order 1: ...
Invoice 1: ...
Payment 1: ...
✅ Test data created with links
🚀 Server listening on 0.0.0.0:3000
$ tree examples/microservice -L 2
examples/microservice
├── config # ✅ Configuration externalisée
│ └── links.yaml
├── module.rs # ✅ Code pur (35 lignes)
├── main.rs
└── entities/
├── order/
├── invoice/
└── payment/
Le nom d’un module doit refléter tout ce qu’il fait, pas une partie.
OrderModule pour Order+Invoice+PaymentBillingModule pour le workflow completLa configuration est de la donnée, pas du code.
Code et config ont des cycles de vie différents.
Plus de lignes ≠ mieux. Séparation ≠ duplication.
La restructuration a permis de :
✅ Clarifier la nomenclature (BillingModule vs OrderModule)
✅ Séparer config et code (YAML externe vs hardcodé)
✅ Simplifier le module (35 lignes vs 90)
✅ Faciliter la maintenance (édition YAML directe)
✅ Améliorer l’évolutivité (multi-environnement possible)
L’architecture respecte maintenant les principes SOLID et les best practices de l’industrie ! 🚀🦀✨