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 migrationV28__reduce_postal_code_for_chaos.sqlré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 uneDataTruncation SQLExceptioncôté JDBC.users.country CHAR(2)— strict ISO 3166-1 alpha-2 ; toute valeur invalide est rejetée parValidationService.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 (bateau → bateaux, 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¶
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.