Image showing Pourquoi

Pourquoi "Utilise simplement Cloudinary" était la mauvaise réponse pour mon API de Génération d'Images

affiliate best offer

[!note] 📚 Série Smart Assets Manager

  1. Pourquoi l’Abstraction de Stockage Est Importante ← vous êtes ici
  2. Quatre Backends, Une Interface — 18 mai
  3. L’API Unifiée : Crédits et Limitation de Débit — 27 avril
  4. Stratégie de Tests : Unitaires vs E2E — 20 avril
  5. 5 Cas Limites qui Brisent les APIs d’Images — 1er juin
  6. Documentation API : Swagger + Postman — 30 mars

Quand j’ai commencé à construire Smart Assets Manager — un système pour générer en masse des images de couverture de blog, des icônes d’app store, et des visuels pour les réseaux sociaux — la question du stockage semblait simple. Générer l’image, la téléverser sur Cloudinary, retourner l’URL. Terminé.

Ça a duré environ deux semaines.

La troisième personne à utiliser l’API est revenue avec une question : « Est-ce que je peux utiliser ça localement pendant le développement sans avoir besoin d’identifiants Cloudinary ? » Bonne question. La réponse, avec un backend Cloudinary codé en dur, était non.

Une semaine plus tard, une demande d’intégration CI/CD est arrivée : l’API était appelée en cours de pipeline pour générer des images Open Graph avant le déploiement. Téléverser sur Cloudinary dans ce contexte créait une dépendance externe susceptible d’échouer indépendamment du déploiement lui-même. L’auteur du pipeline voulait recevoir les octets de l’image directement dans la réponse et les stocker comme bon lui semblait.

Deux semaines après, un cas d’utilisation entreprise est apparu : un client dont la politique de résidence des données interdisait que les images quittent son infrastructure. Cloudinary, en tant que CDN tiers, était totalement exclu.

Trois problèmes différents. Trois exigences différentes. Tous pointant vers la même cause profonde : le générateur et le stockage étaient couplés.

À retenir : Un backend de stockage codé en dur dans une API de génération d’images n’est pas une décision de stockage — c’est une contrainte qui se propage à travers chaque cas d’utilisation auquel vous n’avez pas encore pensé. La question n’est pas de savoir si vous aurez besoin de flexibilité ; c’est de savoir si vous l’aurez construite avant d’en avoir besoin.


Ce que Coûte le Couplage

Le code de l’époque ressemblait approximativement à ceci — le générateur produit des octets, l’appel Cloudinary produit une URL, l’URL est retournée :

def generate_and_store(template_data: dict, width: int, height: int) -> str:
    image_bytes = renderer.render(template_data, width, height)

    # Upload directly to Cloudinary — no abstraction
    result = cloudinary.uploader.upload(image_bytes, public_id=template_data["name"])
    return result["secure_url"]

Propre, simple, fonctionne parfaitement — jusqu’à ce que l’un des trois cas d’utilisation ci-dessus apparaisse. À ce moment-là, cette fonction doit être modifiée. Chaque test qui mocke cloudinary.uploader.upload doit être mis à jour. Chaque endpoint qui appelle cette fonction hérite de Cloudinary comme dépendance. Chaque environnement CI exécutant des tests a besoin que les identifiants Cloudinary soient configurés.

Le changement nécessaire n’était pas de remplacer Cloudinary par autre chose. C’était de déplacer la décision « où va l’image » hors du code de génération et dans un paramètre que l’appelant contrôle. Le générateur doit produire des octets. Ce qui se passe avec ces octets doit être configuré séparément.


Quatre Cas d’Utilisation, Quatre Backends

Les trois demandes décrites ci-dessus, plus l’hébergement en production, ont donné la forme de l’abstraction :

Backend Besoin de l’appelant Ce que fait le stockage
Cloudinary Hébergement production, URLs partageables Téléverse sur CDN, retourne l’URL publique
Système de fichiers local Développement, exigences sur site Écrit sur disque, retourne l’URL localhost
Amazon S3 Charges de travail production sur AWS Téléverse dans un bucket, retourne l’URL S3
Téléchargement direct Pipelines CI/CD, consommateurs d’API Ignore le stockage, retourne un URI data base64 dans la réponse

L’appelant spécifie quel backend utiliser via un paramètre storage dans la requête API. Le générateur ne change pas — seul le backend dispatché par la factory change.

Le backend de téléchargement direct mérite une note : c’était le cas d’utilisation que personne n’avait planifié et qui s’est avéré le plus utilisé par les développeurs. Au lieu de téléverser quelque part, il encode les octets bruts de l’image en base64 et les retourne en ligne dans la réponse API :

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...

Pas d’identifiants requis. Pas de coûts de stockage. Pas de latence aller-retour. Pour le développement, les tests, et les pipelines automatisés où l’image sera de toute façon traitée par le système en aval, c’est la bonne valeur par défaut — et elle n’existe que parce que l’abstraction en a fait un coût de quelques heures plutôt qu’une refactorisation.

[!note] Les backends que vous n’aviez pas prévus Le backend de téléchargement direct — retourner du base64 dans la réponse — était le backend le plus utile que personne n’avait pensé à demander dès le départ. Les abstractions de stockage se justifient en partie par la facilité avec laquelle elles permettent d’ajouter les backends que vous n’aviez pas anticipés.


Ce que l’Abstraction Coûte Réellement

Le pattern qui permet tout cela n’est pas complexe :

  1. Une classe de base abstraite avec deux méthodes requises — upload() retourne une URL, generate_signed_url() retourne une URL limitée dans le temps pour les assets privés
  2. Quatre implémentations concrètes — une classe par backend, chacune implémentant les deux mêmes méthodes
  3. Une fonction factory — prend le paramètre storage de la requête, retourne l’instance de backend appropriée

C’est un seul fichier, environ 250 lignes entre la classe de base et les quatre implémentations. L’investissement est borné et concentré sur le départ. Le bénéfice — la capacité d’ajouter un nouveau backend, de remplacer le backend de production, ou de tester sans identifiants externes — se compose avec chaque fonctionnalité ajoutée au système après sa mise en place.

Rétrofitter cette abstraction après que le système avait grandi aurait nécessité de toucher le générateur, chaque endpoint API, chaque test, et chaque configuration de déploiement. Le coût de le faire dès le départ est toujours inférieur au coût de le faire plus tard. Mais la comparaison ne devient visible qu’avec le recul, ce qui est ce qui fait qu’« utilise simplement Cloudinary » semble un choix raisonnable au début.


La Suite

Le cas architectural est établi. Le prochain article couvre l’implémentation concrète : comment quatre backends de stockage partagent une interface, incluant les contrôles de confidentialité, la logique de nettoyage en cas d’échec, et la détection de signature d’octets qui rend l’inférence de format fiable.

→ Suivant : Quatre Backends, Une Interface

Full Bright

Full Bright

A professional and sympathic business man.

Contact

Contact Us

To order one of our services, navigate to the order service page

Address

10 rue François 1er,
75008 Paris

Email Us

hello at bright-softwares dot com

Open Hours

Monday - Friday
9:00AM - 05:00PM