Skip to content

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)

services/submission-processor/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 = 10
max_batch_timeout = 30
[env.production]
name = "amn-submission-processor-prod"
vars = { ENVIRONMENT = "production" }
[[env.production.queues.consumers]]
queue = "submissions-queue-prod"
max_batch_size = 10
max_batch_timeout = 30

3.4. Injection des secrets

Terminal window
# Injection des secrets via Wrangler CLI
wrangler secret put SANITY_PROJECT_ID --env production
wrangler secret put SANITY_DATASET --env production
wrangler secret put SANITY_API_WRITE_TOKEN --env production

ou 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 bindings
export interface Env {
ENVIRONMENT: string;
SANITY_PROJECT_ID: string;
SANITY_DATASET: string;
SANITY_API_WRITE_TOKEN?: string;
}
// Type de message de soumission
export 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 put ou @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 basiques
console.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

Terminal window
# Logs en temps réel
wrangler tail submission-processor
# Vérification des bindings
wrangler deployments list
# Test de l'endpoint health
curl http://localhost:8789/health
# Test de l'endpoint 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"}'

9. Déploiement

9.1. Pipeline de déploiement

Terminal window
# Build
npm run build --workspace=services/submission-processor
# Déploiement staging
wrangler deploy --env staging
# Déploiement production
wrangler deploy --env production

9.2. Rollback

Terminal window
# Liste des déploiements
wrangler deployments list
# Rollback vers une version précédente
wrangler deployments rollback --deployment-id=[id]

9.3. Tests manuels

Terminal window
# 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-dev

10. 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.sh pour 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.ts
    • services/submission-processor/wrangler.toml
    • services/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
Switch to normal mode

Contents