Les abstractions sont faciles à mal concevoir. Vous les rendez soit trop fines -- exposant des détails spécifiques au backend qui fuient à travers chaque site d'appel -- soit trop épaisses, cachant des capacités dont le code en aval a légitimement besoin. Le trait StorageBackend dans FLIN devait naviguer précisément cet fil : suffisamment abstrait pour que les systèmes de fichiers locaux, S3, Cloudflare R2 et Google Cloud Storage s'intègrent tous derrière la même interface, et suffisamment concret pour que chaque backend puisse optimiser ses opérations sans contorsions.
La définition du trait
Le trait complet a neuf méthodes. Chacune a été choisie parce que les quatre backends en ont besoin, et aucune ne pouvait être exprimée purement en termes des autres :
rustpub trait StorageBackend: Send + Sync {
fn put(&self, hash: &str, data: &[u8], extension: &str) -> StorageResult<String>;
fn put_from_path(&self, hash: &str, temp_path: &str, extension: &str) -> StorageResult<String>;
fn get(&self, path: &str) -> StorageResult<Vec<u8>>;
fn delete(&self, path: &str) -> StorageResult<()>;
fn exists(&self, hash: &str, extension: &str) -> StorageResult<bool>;
fn url(&self, hash: &str, extension: &str) -> String;
fn signed_url(&self, hash: &str, extension: &str, duration: Duration) -> StorageResult<String>;
fn backend_type(&self) -> &'static str;
fn base_path(&self) -> &str;
}Pourquoi Send + Sync
Les deux mots les plus importants dans la définition du trait ne sont pas des noms de méthodes -- ce sont Send + Sync. Ces traits marqueurs Rust garantissent que tout type implémentant StorageBackend peut être partagé entre les threads et envoyé à travers les frontières de threads de manière sûre.
Le patron Arc<dyn StorageBackend> -- un objet trait à comptage de références atomique -- est la façon dont FLIN partage une seule instance de backend entre tous les gestionnaires de requêtes. La contrainte Send + Sync rend ce patron possible au moment de la compilation.
Invariants de sécurité
La conception du trait encode plusieurs invariants de sécurité que chaque backend doit respecter :
Validation du hash. Chaque backend valide le paramètre hash avant de construire un chemin, empêchant les attaques de traversée de chemin.
Comparaison en temps constant. La vérification des URL signées utilise la comparaison d'octets en temps constant pour empêcher les attaques par timing.
Assainissement des extensions. Les extensions de fichiers sont débarrassées des séparateurs de chemin et validées contre une liste blanche de caractères.
Ce que le trait n'inclut pas
Il n'y a pas de méthodes pour lister tous les fichiers, diffuser de gros téléchargements ou gérer des listes de contrôle d'accès. Ceux-ci sont gérés par des systèmes séparés qui se composent avec le backend de stockage plutôt que de l'étendre. Cela garde le trait focalisé -- neuf méthodes, chacune avec un but clair, chacune implémentable par tout système de stockage capable de stocker et récupérer des octets par clé.
Ceci est la partie 127 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.
Navigation de la série : - [126] Stockage de fichiers avec 4 backends - [127] Le patron de trait StorageBackend (vous êtes ici) - [128] Backends R2 et Google Cloud Storage