Image showing Pourquoi « Juste Utiliser Cloudinary » Était la Mauvaise Réponse pour Mon API de Génération d'Images

Pourquoi « Juste Utiliser Cloudinary » Était la Mauvaise Réponse pour Mon API de Génération d'Images

affiliate best offer

📚 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 vedettes de blog, des icônes pour les app stores et des visuels pour les réseaux sociaux — la question du stockage semblait simple. Générer l’image, l’uploader 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 faire tourner ça en local 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 au milieu d’un pipeline pour générer des images Open Graph avant le déploiement. Uploader sur Cloudinary dans ce contexte créait une dépendance externe qui pouvait é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 plus tard, un cas d’usage d’entreprise est apparu : un client dont la politique de résidence des données interdisait que les images quittent leur infrastructure. Cloudinary, en tant que CDN tiers, était hors de question.

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

Enseignement : 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 dans chaque cas d’usage que vous n’avez pas encore envisagé. La question n’est pas de savoir si vous aurez besoin de flexibilité ; c’est de savoir si vous l’aurez construite d’ici là.


Ce que le Couplage Coûte

Le code à ce moment-là 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 directement vers Cloudinary — aucune abstraction
    result = cloudinary.uploader.upload(image_bytes, public_id=template_data["name"])
    return result["secure_url"]

Propre, simple, ça fonctionne parfaitement — jusqu’à ce que l’un des trois cas d’usage 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 qui exécute les 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 arrive à ces octets doit être configuré séparément.


Quatre Cas d’Usage, 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 le stockage fait
Cloudinary Hébergement en production, URLs partageables Upload vers CDN, retourne une URL publique
Système de fichiers local Développement, exigences on-premise Écriture sur disque, retourne une URL localhost
Amazon S3 Charges de travail en production sur infrastructure AWS Upload vers bucket, retourne une URL S3
Téléchargement direct Pipelines CI/CD, consommateurs d’API Contourne 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’usage que personne n’avait prévu et qui s’est avéré être le plus utilisé par les développeurs. Au lieu d’uploader quoi que ce soit, il encode en base64 les octets bruts de l’image et les retourne directement dans la réponse API :

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

Aucun identifiant requis. Aucun coût de stockage. Aucune latence d’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 le bon défaut — et il n’existe que parce que l’abstraction a rendu son ajout possible en quelques heures plutôt qu’en un refactoring.

Les backends que vous ne prévoyez pas 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 à l’avance. Les abstractions de stockage se justifient en partie par le faible coût qu’elles permettent d’ajouter les backends que vous n’aviez pas anticipés.


Ce que l’Abstraction Coûte Vraiment

Le pattern qui rend tout cela possible 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 réparties entre la classe de base et les quatre implémentations. L’investissement est borné et frontchargé. Le bénéfice — la possibilité d’ajouter un nouveau backend, de remplacer le backend de production, ou de tester sans identifiants externes — se compose à chaque fonctionnalité ajoutée au système après sa mise en place.

Refactoriser cette abstraction après que le système a 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 en amont est toujours inférieur au coût de le faire plus tard. Mais la comparaison ne devient visible qu’avec le recul, ce qui fait que « juste utiliser Cloudinary » semble être un choix raisonnable au départ.


La Suite

L’argument architectural est établi. Le prochain article couvre l’implémentation réelle : comment quatre backends de stockage partagent une seule interface, y compris 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