Aller au contenu

Schéma de données

PerfShop persiste ses données dans MySQL 8 via JPA / Hibernate, avec un schéma versionné par Flyway (migrations V1 à V38). Cette page décrit les sept entités principales, leurs relations, et l'évolution du schéma au fil des migrations.

Source de vérité

Les diagrammes et tableaux de cette page sont extraits directement des classes @Entity du package com.perfshop.entity et des fichiers SQL du dossier backend/src/main/resources/db/migration/. Les noms de colonnes, les types et les contraintes correspondent strictement au code et aux migrations.

Entités JPA

Le modèle de données comporte sept entités persistantes, organisées en deux îlots :

  • L'îlot e-commerce : User, Product, CartItem, Order, OrderItem — c'est la boutique classique avec les produits, le panier, les commandes.
  • L'îlot administration et pédagogie : AdminUser, PedagogiqueSessionEntity — comptes formateur et persistance des sessions étudiant.
erDiagram
  USERS ||--o{ CART_ITEMS : "ajoute au panier"
  USERS ||--o{ ORDERS : "passe"
  ORDERS ||--o{ ORDER_ITEMS : "contient"
  PRODUCTS ||--o{ CART_ITEMS : "référencé par"
  PRODUCTS ||--o{ ORDER_ITEMS : "vendu via"

  USERS {
    BIGINT      id PK
    VARCHAR_100 email UK
    VARCHAR_255 password "BCrypt strength 10"
    VARCHAR_100 first_name
    VARCHAR_100 last_name
    VARCHAR_5   civility "M / Mme / Mx"
    DATE        birth_date
    VARCHAR_20  phone
    TEXT        street
    VARCHAR_5   postal_code "court intentionnel — chaos métier"
    VARCHAR_100 city
    VARCHAR_100 region
    CHAR_2      country "ISO 3166-1 alpha-2, défaut FR"
    DATETIME    created_at
    DATETIME    last_login
    BOOLEAN     is_pedagogique "agent BAC1-5"
  }

  PRODUCTS {
    BIGINT       id PK
    VARCHAR_200  name
    VARCHAR_1000 description
    DECIMAL_10_2 price
    INT          stock
    VARCHAR_50   category
    VARCHAR_500  image_url
    DATETIME     created_at
    DATETIME     updated_at
    BOOLEAN      is_pedagogique "produit secret du parcours"
    BOOLEAN      pedagogique_protected "stock jamais décrémenté"
  }

  CART_ITEMS {
    BIGINT       id PK
    BIGINT       user_id FK
    VARCHAR_255  session_id "panier anonyme"
    BIGINT       product_id FK
    INT          quantity
    DECIMAL_10_2 price
    DATETIME     added_at
  }

  ORDERS {
    BIGINT       id PK
    VARCHAR_50   order_number UK
    BIGINT       user_id FK
    DECIMAL_10_2 total_amount
    VARCHAR_20   status "PENDING/CONFIRMED/.../CANCELLED"
    VARCHAR_500  shipping_address
    VARCHAR_50   shipping_method
    VARCHAR_50   payment_method
    VARCHAR_4    card_last4 "4 derniers chiffres uniquement"
    BOOLEAN      cvv_verified
    BOOLEAN      is_test_data "commande agent pédagogique"
    DATETIME     created_at
    DATETIME     updated_at
  }

  ORDER_ITEMS {
    BIGINT       id PK
    BIGINT       order_id FK
    BIGINT       product_id FK
    INT          quantity
    DECIMAL_10_2 unit_price
    DECIMAL_10_2 subtotal
  }

Tables hors îlot e-commerce

erDiagram
  ADMIN_USERS {
    BIGINT      id PK
    VARCHAR_100 email UK
    VARCHAR_255 password_hash "BCrypt strength 10"
    BOOLEAN     can_access_chaos
    BOOLEAN     can_access_monitoring
    BOOLEAN     can_access_admin
    BOOLEAN     can_access_jmeter
    BOOLEAN     can_access_scripts
    BOOLEAN     is_superadmin "indestructible"
    DATETIME    created_at
  }

  PEDAGOGIQUE_SESSIONS {
    VARCHAR_36  token PK "UUID complet"
    VARCHAR_100 alias
    VARCHAR_10  level "bac1 .. bac5"
    BIGINT      joined_at "epoch ms"
    BIGINT      completed_at "0 si en cours"
    INT         current_step
    VARCHAR_10  agent_code "4 hex digits"
    VARCHAR_64  extraction_answer_hash "SHA-256"
    VARCHAR_20  logique_question_indices "ex: 15,10,18,19,14"
    VARCHAR_64  logique_expected_hash "SHA-256"
    TEXT        attempts_json "JSON tentatives par étape"
    TIMESTAMP   created_at
  }

  PERFSHOP_LICENSE {
    BIGINT      id PK
    TEXT        license_key
    DATETIME    uploaded_at
  }

Pas de clé étrangère vers users pour pedagogique_sessions

Les sessions pédagogiques sont identifiées uniquement par leur token UUID (header HTTP X-Student-Token). Aucun compte utilisateur n'est requis pour rejoindre un parcours — l'étudiant clique simplement sur le lien fourni par le formateur. Cette absence de FK est intentionnelle et documentée dans la migration V36.

Diagramme de classes — vue Java

Pour les développeurs qui lisent le code, voici la même information sous forme de diagramme de classes UML, avec les relations JPA explicites :

classDiagram
  class User {
    +Long id
    +String email
    +String password
    +String firstName
    +String lastName
    +String civility
    +LocalDate birthDate
    +String street
    +String postalCode
    +String city
    +String country
    +LocalDateTime createdAt
    +LocalDateTime lastLogin
    +boolean isPedagogique
    +onCreate()
  }

  class Product {
    +Long id
    +String name
    +String description
    +BigDecimal price
    +Integer stock
    +String category
    +String imageUrl
    +boolean isPedagogique
    +boolean pedagogiqueProtected
    +onCreate()
    +onUpdate()
  }

  class CartItem {
    +Long id
    +User user
    +String sessionId
    +Product product
    +Integer quantity
    +BigDecimal price
    +LocalDateTime addedAt
  }

  class Order {
    +Long id
    +String orderNumber
    +User user
    +BigDecimal totalAmount
    +OrderStatus status
    +String shippingAddress
    +String paymentMethod
    +String cardLast4
    +boolean cvvVerified
    +boolean testData
    +List~OrderItem~ orderItems
  }

  class OrderItem {
    +Long id
    +Order order
    +Product product
    +Integer quantity
    +BigDecimal unitPrice
    +BigDecimal subtotal
  }

  class AdminUser {
    +Long id
    +String email
    +String passwordHash
    +boolean canAccessChaos
    +boolean canAccessMonitoring
    +boolean canAccessAdmin
    +boolean canAccessJmeter
    +boolean canAccessScripts
    +boolean superAdmin
  }

  class PedagogiqueSessionEntity {
    +String token
    +String alias
    +String level
    +long joinedAt
    +long completedAt
    +int currentStep
    +String agentCode
    +String extractionAnswerHash
    +String attemptsJson
    +displayAlias() String
  }

  User "1" --> "*" CartItem : possède
  User "1" --> "*" Order : passe
  Order "1" --> "*" OrderItem : contient
  Product "1" --> "*" CartItem : référencé
  Product "1" --> "*" OrderItem : vendu

Annotations Lombok et JPA

Toutes les entités utilisent les annotations Lombok @Data (ou @Getter/@Setter) pour générer les accesseurs, et JPA standard pour le mapping :

Annotation Usage typique dans PerfShop
@Entity + @Table(name=...) Mapping classe ↔ table SQL
@Id + @GeneratedValue(strategy=IDENTITY) PK auto-incrémentée
@Column(name=..., length=..., nullable=..., unique=...) Contraintes de colonne
@Index(name=..., columnList=...) Index pour performance et chaos métier
@ManyToOne(fetch=LAZY/EAGER) + @JoinColumn Relations N-1
@OneToMany(mappedBy=..., cascade=ALL, orphanRemoval=true) Relations 1-N (Order → OrderItem)
@Enumerated(EnumType.STRING) Stockage enum (ex. OrderStatus) en VARCHAR
@PrePersist / @PreUpdate Hooks sur created_at / updated_at
@Serial + Serializable Marquage des entités session-portables

Index et contraintes notables

Table Index Justification
users idx_email (UNIQUE) Lookup login + unicité email
users idx_country Filtrage par pays (chaos métier A1–A16)
products idx_category Filtrage catalogue par catégorie
products idx_name Recherche full-text simple
cart_items idx_user_id, idx_session_id Récupération panier (utilisateur OU session anonyme)
orders idx_user_id, idx_order_number (UNIQUE), idx_created_at Historique commandes utilisateur, lookup numéro, tri chronologique
admin_users idx_admin_email (UNIQUE) Login admin et unicité
pedagogique_sessions idx_ped_sessions_level, idx_ped_sessions_joined_at, idx_ped_sessions_completed Stats temps réel formateur (par niveau, par cohorte temporelle, par état)

Contraintes pédagogiquement « cassées »

Certaines colonnes ont des longueurs volontairement insuffisantes pour servir de support aux scénarios de chaos métier :

  • users.postal_code VARCHAR(5) — la migration V28__reduce_postal_code_for_chaos.sql réduit ce champ de 10 à 5 caractères. Le chaos métier de niveau 2+ injecte délibérément une valeur de plus de 5 caractères pour provoquer une DataTruncation SQLException côté JDBC.
  • users.country CHAR(2) — strict ISO 3166-1 alpha-2 ; toute valeur invalide est rejetée par ValidationService.validateCountry().

Migrations Flyway

Le schéma est versionné par 38 migrations Flyway dans backend/src/main/resources/db/migration/. Flyway est activé via spring.flyway.enabled=true et baseline-on-migrate=true (cf. application.yml). Les migrations sont chargées depuis classpath:db/migration-${PERFSHOP_LANG:fr} — il existe ainsi deux jeux de migrations parallèles FR et EN pour les données de test (catalogue produits traduit), tout en partageant les migrations de schéma.

Vue chronologique groupée par thème

Thème Migrations Rôle
Schéma initial V1, V2 Création des 5 tables e-commerce + jeu de données de test minimal
Catalogue produits — extensions V5, V7, V12, V13, V14, V15, V16, V17, V18, V19, V21, V23, V24 Ajout progressif des catégories : informatique, avions, voitures, entreprises, hélicoptères, bateaux, motos
Images produits V6, V8, V20 Migration des URLs d'images vers des chemins locaux (/images/products/...)
Normalisation catégories V22, V25 Harmonisation des libellés de catégories (bateaubateaux, etc.)
Statut commande V3 Correction de l'enum OrderStatus
Schéma — précision prix V11 Passage de DECIMAL(8,2) à DECIMAL(10,2)
Profil utilisateur étendu V9, V10, V26, V27 Ajout des champs profil (civilité, naissance, adresse complète, pays), correction du type ENUM civility
Chaos métier — code postal V28 Réduction de postal_code à VARCHAR(5) pour permettre la DataTruncation pédagogique
Sécurité — hash mots de passe V29 Migration des mots de passe en clair vers BCrypt strength 10
Comptes admin V30, V31 Création de la table admin_users puis ajout des droits can_access_jmeter et can_access_scripts
Licence V32 Création de la table perfshop_license
Pédagogique — produits et infrastructure V33, V34 Produits pédagogiques secrets + protection stock + comptes agent BAC1-5
Pédagogique — flag données de test V35 Colonne is_test_data sur orders (commandes des agents pédagogiques exclues des stats par défaut)
Pédagogique — sessions persistantes V36 Table pedagogique_sessions (architecture write-through)
Pédagogique — agents prefilled V37 Insertion des profils agent BAC1-5 (noms thématiques espionnage)
Pédagogique — thème logique V38 Colonnes logique_question_indices et logique_expected_hash (calculées au /join, validation côté serveur)

Convention de nommage

V<numéro>__<description_snake_case>.sql

Exemples : V1__Initial_schema.sql, V28__reduce_postal_code_for_chaos.sql, V36__add_pedagogique_sessions_table.sql.

Lecture des migrations dans l'ordre

Les migrations Flyway s'appliquent dans l'ordre numérique des préfixes V<n>. La progression chronologique du dossier db/migration/ raconte l'évolution réelle de PerfShop : on y voit le passage d'une boutique simple à une plateforme pédagogique multi-niveau, avec l'ajout progressif des supports pour chaque famille de chaos.

Connexion et pool

La connexion à MySQL est configurée dans application.yml :

Paramètre Valeur
Driver com.mysql.cj.jdbc.Driver
URL (compose) jdbc:mysql://perfshop-db:3306/perfshop?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
Pool HikariCP, nom PerfShopHikariPool
maximum-pool-size 20
minimum-idle 5
connection-timeout 30 s
idle-timeout 10 min
max-lifetime 30 min
ddl-auto validate (Hibernate vérifie le schéma au démarrage, ne le modifie pas)
dialect org.hibernate.dialect.MySQLDialect
Format SQL format_sql=true, use_sql_comments=true
Niveau de log SQL WARN (Hibernate SQL et binders en WARN volontairement, pour ne pas faire enfler la heap en idle)

Le pool HikariCP est exposé en métriques Prometheus via Micrometer (hikaricp_connections_active, hikaricp_connections_idle, hikaricp_connections_max, hikaricp_connections_pending, hikaricp_connections_acquire_seconds). Ces métriques alimentent les dashboards Backend Élève et Formateur — voir observability/dashboards.md.