Submission Processor
Submission Processor
Documentation technique
Type de ressource : Cloudflare Worker (Microservice FaaS / Queue Consumer)
Fichier source :services/submission-processor/src/index.ts
Statut : Actif
1. Vue d’ensemble
1.1. Description
Worker Cloudflare qui consomme les messages de la Queue submissions-queue et crée des documents dans Sanity. Traite les soumissions de formulaire de manière asynchrone via un Queue consumer. Inclut un endpoint de test direct (/test-direct) pour le développement local.
1.2. Objectif
- Consommer les soumissions de formulaire depuis la Queue Cloudflare
- Créer des documents Sanity pour chaque soumission
- Fournir un endpoint de test direct pour le développement local
- Gérer les erreurs avec retry exponentiel
1.3. Caractéristiques
| Propriété | Valeur |
|---|---|
| Type | Microservice métier / Queue Consumer |
| Trigger | Queue consumer, HTTP fetch |
| Route publique | /health, /test-direct |
| Environnements | Development, Production |
| Consommateurs | Form-handler (producer), Sanity CMS |
2. Architecture et flux
[Entrée : Queue Cloudflare] │ • Message de soumission ▼[Submission-processor Worker] │ • Queue consumer │ • Initialisation du client Sanity │ • Création du document Sanity ▼[Sanity CMS] │ • Document submission créé ▼[Confirmation (ack/retry)]2.2. Positionnement dans la chaîne Edge
[Client] │ ▼[Astro Frontend] │ ▼[Form Handler Worker] │ • Producer Queue ▼[Queue Cloudflare] │ ▼[Submission-processor Worker] │ • Consumer Queue ▼[Sanity CMS]2.3. Composants impliqués
| Composant | Rôle | Type |
|---|---|---|
| SUBMISSIONS_QUEUE | Queue asynchrone | Cloudflare Binding |
| Sanity Client | Client API Sanity | Bibliothèque (@sanity/client) |
| Sanity API | Stockage des soumissions | Service tiers |
| Form-handler | Producer de la Queue | Cloudflare Worker |
3. Configuration
3.1. Variables d’environnement
| Variable | Description | Obligatoire | Valeur par défaut |
|---|---|---|---|
ENVIRONMENT |
Environnement (dev/production) | ✅ | dev |
SANITY_PROJECT_ID |
ID du projet Sanity | ✅ | Injecté via secret |
SANITY_DATASET |
Nom du dataset Sanity | ✅ | Injecté via secret |
SANITY_API_WRITE_TOKEN |
Token d’écriture Sanity | ✅ | Injecté via secret |
3.2. Bindings Cloudflare
| Binding | Type | Nom | Description |
|---|---|---|---|
| SUBMISSIONS_QUEUE | Queue Consumer | SUBMISSIONS_QUEUE | Queue pour les soumissions |
3.3. Configuration Wrangler (wrangler.toml)
name = "amn-submission-processor"main = "src/index.ts"compatibility_date = "2025-07-18"compatibility_flags = ["nodejs_compat"]
[vars]ENVIRONMENT = "dev"
[env.dev]name = "amn-submission-processor-dev"vars = { ENVIRONMENT = "dev" }
[[env.dev.queues.consumers]]queue = "submissions-queue-dev"max_batch_size = 10max_batch_timeout = 30
[env.production]name = "amn-submission-processor-prod"vars = { ENVIRONMENT = "production" }
[[env.production.queues.consumers]]queue = "submissions-queue-prod"max_batch_size = 10max_batch_timeout = 303.4. Injection des secrets
# Injection des secrets via Wrangler CLIwrangler secret put SANITY_PROJECT_ID --env productionwrangler secret put SANITY_DATASET --env productionwrangler secret put SANITY_API_WRITE_TOKEN --env productionou via le script @inject_secret_to_cloudflare.sh du projet.
4. Implémentation
4.1. Code de production (src/index.ts)1/2
4.1. Code de production (src/index.ts)2/2
import { createClient } from '@sanity/client'
export interface Env { ENVIRONMENT: string; SANITY_PROJECT_ID: string; SANITY_DATASET: string; SANITY_API_WRITE_TOKEN?: string;}
export interface SubmissionMessage { id: string; name: string; email: string; message: string; submittedAt: string; environment: string;}
export default { async fetch(request: Request, env: Env): Promise<Response> { const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') { return new Response(JSON.stringify({ status: 'healthy', service: 'submission-processor', environment: env.ENVIRONMENT ?? 'dev', sanityProjectIdPrefix: env.SANITY_PROJECT_ID ? env.SANITY_PROJECT_ID.slice(0, 4) + '…' : 'NOT SET', sanityDataset: env.SANITY_DATASET ?? 'NOT SET', timestamp: new Date().toISOString(), }), { status: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); }
// Test endpoint for direct Sanity integration (bypasses Queue for local testing) if (url.pathname === '/test-direct' && request.method === 'POST') { try { const data = await request.json() as { name: string; email: string; message: string };
const sanityClient = createClient({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: '2024-01-01', token: env.SANITY_API_WRITE_TOKEN, useCdn: false, });
const result = await sanityClient.create({ _type: 'submission', name: data.name, email: data.email, message: data.message, status: 'pending', submittedAt: new Date().toISOString(), processedAt: new Date().toISOString(), });
return new Response(JSON.stringify({ success: true, sanityId: result._id, message: 'Direct test: submission stored in Sanity' }), { headers: { 'Content-Type': 'application/json' }, }); } catch (error) { console.error('Direct test error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return new Response(JSON.stringify({ success: false, error: errorMessage, }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } }
if (request.method === 'OPTIONS') { return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }, }); }
return new Response(JSON.stringify({ error: 'Not Found', hint: 'Try GET /health or POST /test-direct for direct Sanity testing' }), { status: 404, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); },
async queue(batch: MessageBatch<SubmissionMessage>, env: Env): Promise<void> { console.log(`Processing batch of ${batch.messages.length} submissions`);
// Initialize Sanity client const sanityClient = createClient({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: '2024-01-01', token: env.SANITY_API_WRITE_TOKEN, useCdn: false, });
for (const message of batch.messages) { try { const submission = message.body as SubmissionMessage; console.log(`Processing submission ${submission.id} from ${submission.email}`);
// Create document in Sanity const result = await sanityClient.create({ _type: 'submission', name: submission.name, email: submission.email, message: submission.message, status: 'pending', submittedAt: submission.submittedAt, processedAt: new Date().toISOString(), });
console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);
// Acknowledge message message.ack();
} catch (error) { console.error(`Failed to process submission:`, error);
// Retry with exponential backoff message.retry({ delaySeconds: Math.min(60, Math.pow(2, batch.messages.indexOf(message))), }); } } },};4.2. Interfaces et types
// Interface des bindingsexport interface Env { ENVIRONMENT: string; SANITY_PROJECT_ID: string; SANITY_DATASET: string; SANITY_API_WRITE_TOKEN?: string;}
// Type de message de soumissionexport interface SubmissionMessage { id: string; name: string; email: string; message: string; submittedAt: string; environment: string;}5. Sécurité
5.1. Mécanismes de sécurité
| Mécanisme | Implémentation | Rationale |
|---|---|---|
| Secrets injectés | Secrets Sanity via wrangler secret put | Protection des credentials |
| CORS | Headers CORS permissifs | Permettre les requêtes cross-origin |
| Masquage de secrets | Prefixe du project ID masqué dans /health | Protection contre l’exposition |
5.2. Validation des entrées
Non applicable - les données sont déjà validées par form-handler.
5.3. Gestion des secrets
- ✅ Les secrets ne sont jamais dans le code source
- ✅ Injection via
wrangler secret putou@inject_secret_to_cloudflare.sh - ✅ Rotation régulière des secrets (tous les 90 jours recommandé)
6. Résilience et gestion des erreurs
6.1. Stratégie de retry
| Scénario | Stratégie | Rationale |
|---|---|---|
| Sanity API error | Retry exponentiel (max 60s) | Tolérance aux pannes temporaires |
| Sanity API success | Ack message | Confirmation du traitement |
6.2. Safety Locks
Non implémenté - pas de lock configuré.
6.3. Gestion des timeouts
- ✅ Timeout global : Dépend de Cloudflare (30s Free / 50s Paid)
- ✅ Timeout par service tiers : Dépend de Cloudflare
- ✅ Retry exponentiel pour les erreurs Sanity
7. Performance et limites
7.1. Limites Cloudflare Workers
| Limite | Valeur | Stratégie d’optimisation |
|---|---|---|
| Mémoire RAM | 128 MB | Pas de traitement lourd en mémoire |
| CPU time | 30s (Free) / 50s (Paid) | Batch size limité à 10 |
| Nombre de sous-requêtes | 50 | Limité par batch size |
| Taille requête/réponse | 100 MB | Payload de soumission limité |
7.2. Optimisations appliquées
- ✅ Traitement par batch (max 10 messages)
- ✅ Retry exponentiel pour les erreurs
- ✅ Endpoint de test direct pour le développement local
8. Monitoring et observabilité
8.1. Logs structurés
// Logs console.log basiquesconsole.log(`Processing batch of ${batch.messages.length} submissions`);console.log(`Processing submission ${submission.id} from ${submission.email}`);console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);console.error(`Failed to process submission:`, error);8.2. Métriques clés
Non implémenté - aucune métrique ou alerte n’est configurée.
8.3. Commandes de debug
# Logs en temps réelwrangler tail submission-processor
# Vérification des bindingswrangler deployments list
# Test de l'endpoint healthcurl http://localhost:8789/health
# Test de l'endpoint test-directcurl -X POST http://localhost:8789/test-direct \ -H "Content-Type: application/json" \ -d '{"name":"Test","email":"test@example.com","message":"Test message"}'9. Déploiement
9.1. Pipeline de déploiement
# Buildnpm run build --workspace=services/submission-processor
# Déploiement stagingwrangler deploy --env staging
# Déploiement productionwrangler deploy --env production9.2. Rollback
# Liste des déploiementswrangler deployments list
# Rollback vers une version précédentewrangler deployments rollback --deployment-id=[id]9.3. Tests manuels
# Test HTTP (health check)curl http://localhost:8789/health
# Test HTTP (test-direct)curl -X POST http://localhost:8789/test-direct \ -H "Content-Type: application/json" \ -d '{"name":"Test","email":"test@example.com","message":"Test message"}'
# Test Queue (via wrangler)wrangler queues tail submissions-queue-dev10. Débogage et troubleshooting
10.1. Problèmes courants
| Symptôme | Cause probable | Solution |
|---|---|---|
| Sanity API error | Token invalide ou manquant | Vérifier les secrets injectés |
| Queue non consommée | Binding mal configuré | Vérifier wrangler.toml |
| Test-direct échoue | Credentials Sanity manquants | Vérifier .env local |
10.2. Erreurs fréquentes
| Code HTTP / Erreur | Signification | Action |
|---|---|---|
| 500 | Erreur Sanity API | Vérifier les logs et les credentials |
| 404 | Endpoint inexistant | Vérifier le pathname |
11. Bonnes pratiques
11.1. Architecture
- ✅ Découpler le traitement de la validation
- ✅ Utiliser les bindings Cloudflare plutôt que des appels HTTP internes
- ✅ Traitement par batch pour optimiser les performances
11.2. Code
- ✅ Typer strictement les bindings et les réponses
- ✅ Gérer explicitement les erreurs (try/catch)
- ✅ Logger les erreurs et les succès
- ✅ Endpoint de test direct pour le développement local
11.3. Sécurité
- ✅ Ne jamais exposer les secrets côté client
- ✅ Masquer les secrets dans les logs
- ✅ Utiliser
@inject_secret_to_cloudflare.shpour l’injection
11.4. Performance
- ✅ Traitement par batch (max 10 messages)
- ✅ Retry exponentiel pour les erreurs
- ✅ Timeout de batch (30s)
12. Évolutions et roadmap
12.1. Améliorations planifiées
- Configuration de Logpush pour les logs
- Ajout de métriques et alertes
- Ajout de structured logging (JSON)
- Amélioration de la gestion des erreurs (DLQ)
12.2. Limitations connues
- ❌ CORS permissif (
*) - à restreindre pour la production - ❌ Pas de Dead Letter Queue (DLQ)
- ❌ Pas de logs structurés
- ❌ Pas de monitoring configuré
- ⚠️ Endpoint
/test-directà désactiver en production
13. Ressources liées1/2
- Documentation Cloudflare Workers : Getting Started
- Documentation Cloudflare Queues : Queues
- Documentation Sanity : Client API
- Workers associés :
- form-handler
13. Ressources liées2/2
- Fichiers concernés :
services/submission-processor/src/index.tsservices/submission-processor/wrangler.tomlservices/submission-processor/package.json@inject_secret_to_cloudflare.sh
14. Historique des modifications
| Date | Version | Description | Auteur |
|---|---|---|---|
| 2026-09-15 | 1.0 | Création initiale | Devin |