Files
roadwave/docs/adr/011-orm-acces-donnees.md
jpgiannetti 852f6d5e16 refactor(docs): réorganiser ADR et règles métier pour clarté
**Changements majeurs** :

1. **Suppression ADR-010 (Commandes volant et likes)** :
   - Contenu consolidé dans Règle 05 (section 5.3)
   - Raison : ADR-010 était du métier déguisé en architecture
   - Section "Implémentation Technique" ajoutée à Règle 05
   - Pattern correct (addition) vs incorrect (multiplication)

2. **Déplacement ADR-011 → Compliance** :
   - `docs/adr/011-conformite-stores.md` → `docs/compliance/stores-submission.md`
   - Raison : Nature opérationnelle/légale, pas architecture technique
   - Nouveau dossier `/docs/compliance/` créé

3. **Renumérotation ADR (010-022)** :
   - Combler les trous de numérotation (010 et 011)
   - ADR-012→010, ADR-013→011, ..., ADR-024→022
   - 22 ADR numérotés en continu (001-022)
   - Historique Git préservé (git mv)

4. **Mise à jour références** :
   - Règle 03 : ADR-010 → Règle 05 (section 5.3)
   - Règle 09 : ADR-010 → Règle 05 (section 5.3)
   - INCONSISTENCIES-ANALYSIS.md : toutes références mises à jour
   - Incohérence #15 annulée (faux problème : modes séparés)

**Résultat** :
-  Séparation claire ADR (technique) vs Règles métier (fonctionnel)
-  Documentation compliance séparée
-  Numérotation ADR continue sans trous
-  Single Source of Truth (pas de redondance)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-01 14:34:12 +01:00

5.8 KiB

ADR-011 : ORM et Accès Données

Statut : Accepté Date : 2025-01-20

Contexte

RoadWave nécessite des requêtes SQL complexes (PostGIS géospatiales) avec performance optimale et type safety. Le choix entre ORM, query builder ou SQL brut impacte maintenabilité et performance.

Décision

sqlc pour génération de code Go type-safe depuis SQL.

Alternatives considérées

Solution Performance Type Safety Contrôle SQL Courbe apprentissage
sqlc Excellente Très haute Total Faible
GORM Moyenne Moyenne Limité Faible
pgx + SQL brut Excellente Faible Total Moyenne
sqlx Bonne Faible Total Faible

Justification

  • Performance : Génération compile-time, zero overhead runtime
  • Type safety : Structs Go générées automatiquement, erreurs détectées à la compilation
  • Contrôle SQL : Requêtes PostGIS complexes écrites en pur SQL (pas de limitations ORM)
  • Maintenabilité : Modifications SQL → sqlc generate → code mis à jour
  • Simplicité : Pas de magic, code généré lisible et debuggable

Workflow

-- queries/content.sql
-- name: GetContentNearby :many
SELECT id, title, ST_Distance(location, $1::geography) as distance
FROM contents
WHERE ST_DWithin(location, $1::geography, $2)
ORDER BY distance
LIMIT $3;
sqlc generate
// Code Go type-safe généré automatiquement
contents, err := q.GetContentNearby(ctx, location, radius, limit)

Gestion des Types PostGIS

Problème : sqlc génère du code depuis SQL, mais les types PostGIS (geography, geometry) ne mappent pas naturellement en Go → types opaques ([]byte, interface{}), perte de type safety.

Solution : Wrappers Typés + Fonctions de Conversion

Architecture :

SQL (PostGIS)
    ↓ ST_AsGeoJSON() / ST_AsText() / pgtype.Point
    ↓
Code Go (wrapper types)
    ↓
Business Logic (strongly-typed)

Implémentation :

// backend/internal/geo/types.go
package geo

import (
    "database/sql/driver"
    "encoding/json"
    "github.com/jackc/pgx/v5/pgtype"
)

// GeoJSON représente un point géographique en format JSON
type GeoJSON struct {
    Type        string    `json:"type"` // "Point"
    Coordinates [2]float64 `json:"coordinates"` // [lon, lat]
}

// Value() et Scan() implémentent sql.Valuer et sql.Scanner
// pour conversion automatique avec sqlc
func (g GeoJSON) Value() (driver.Value, error) {
    return json.Marshal(g)
}

func (g *GeoJSON) Scan(value interface{}) error {
    bytes, _ := value.([]byte)
    return json.Unmarshal(bytes, g)
}

// Distance calcule la distance entre 2 points (Haversine)
func (g GeoJSON) Distance(other GeoJSON) float64 {
    // Implémentation Haversine formula
    // ...
}

// WKT représente un point en Well-Known Text
type WKT string // "POINT(2.3522 48.8566)"

func (w WKT) Scan(value interface{}) error {
    if str, ok := value.(string); ok {
        *w = WKT(str)
    }
    return nil
}

Patterns SQL Recommandés

Pattern 1 : GeoJSON (recommandé pour frontend)

-- queries/poi.sql
-- name: GetPOIsNearby :many
SELECT
    id,
    name,
    ST_AsGeoJSON(location)::jsonb as location,  -- ← Conversion en JSON
    ST_Distance(location, $1::geography) as distance_meters
FROM points_of_interest
WHERE ST_DWithin(location, $1::geography, $2)
ORDER BY distance_meters
LIMIT $3;
// Code généré par sqlc
type GetPOIsNearbyRow struct {
    ID                int64
    Name              string
    Location          json.RawMessage // ← Peut être parsé en GeoJSON
    DistanceMeters    float64
}

// Utilisation
rows, err := q.GetPOIsNearby(ctx, userLocation, radius, limit)
for _, row := range rows {
    var poi geo.GeoJSON
    json.Unmarshal(row.Location, &poi)
    // poi est maintenant strongly-typed
}

Pattern 2 : WKT (pour debug/logging)

-- name: GetPOILocation :one
SELECT
    id,
    ST_AsText(location) as location_wkt  -- ← "POINT(2.3522 48.8566)"
FROM points_of_interest
WHERE id = $1;

Pattern 3 : Utiliser pgtype pour types natifs

-- name: GetDistanceBetweenPOIs :one
SELECT
    ST_Distance(
        (SELECT location FROM points_of_interest WHERE id = $1),
        (SELECT location FROM points_of_interest WHERE id = $2)
    )::float8 as distance_meters;  -- ← Force conversion en float64

Index PostGIS pour Performance

Créer un index GIST pour optimiser les requêtes ST_DWithin :

-- migrations/002_add_postgis_indexes.up.sql
CREATE INDEX idx_poi_location_gist
ON points_of_interest USING GIST(location);

CREATE INDEX idx_user_last_position_gist
ON user_locations USING GIST(last_position);

Checklist d'Implémentation

  • Créer package backend/internal/geo/types.go avec wrappers
  • Implémenter Scan/Value pour conversion automatique
  • Écrire requêtes SQL avec ST_AsGeoJSON() / ST_AsText()
  • Ajouter index GIST sur colonnes géographiques
  • Documenter patterns dans backend/README.md
  • Tests d'intégration avec Testcontainers (PostGIS réel)

Impact

  • Type safety retrouvée : Plus de interface{} opaques
  • Performance : Index GIST + conversion optimisée
  • Maintenabilité : Patterns clairs, réutilisables
  • Complexité : Une couche de plus, mais justifiée

Référence : Résout incohérence #4 dans INCONSISTENCIES-ANALYSIS.md

Conséquences

  • Dépendance : github.com/sqlc-dev/sqlc
  • Fichier sqlc.yaml à la racine pour configuration
  • Migrations gérées séparément avec golang-migrate
  • CI doit exécuter sqlc generate pour valider cohérence SQL/Go

Librairies : Voir ADR-018 pour stack complet (sqlc + golang-migrate + pgx)