Skip to content

Migration Spring boot 4.0.0

Rapport de migration Spring Boot 3.5.0 → 4.0.0

🎯 Objectifs de la migration

  • Mettre à jour l’application OreSiNg vers Spring Boot 4.0.0 (Spring Framework 7).
  • Garder le comportement fonctionnel identique (tests verts).
  • 🤝 Fournir un retour d’expérience pour les prochaines migrations internes.
  • 🧾 Identifier les dettes techniques introduites pendant la migration.

🧱 Base technique et dépendances

Spring Boot et BOM

  • Migration du parent:
    • spring-boot-starter-parent 3.5.0 → 4.0.0.
  • Introduction du BOM Testcontainers:
    • Ajout de org.testcontainers:testcontainers-bom:2.0.2 dans dependencyManagement pour aligner toutes les libs Testcontainers.

🔍 Points à surveiller:

  • ⚠️ Ne pas doubler les versions Testcontainers (éviter de mixer postgresql:1.x et testcontainers-postgresql:2.x).
  • ⚠️ Vérifier que toutes les dépendances Spring Boot (starter-test, starter-webflux, etc.) sont bien pilotées par le parent et non redéclarées avec des versions différentes.

Testcontainers PostgreSQL

  • Passage de:
    • org.testcontainers:postgresql:1.21.3
  • à:
    • org.testcontainers:testcontainers-postgresql (2.0.2).

🎯 Effet:

  • Intégration plus propre avec Spring Boot 4 et suppression des problèmes de classes “shaded”.
  • Utilisation d’un bean de test dédié (TestDatabaseConfig) pour démarrer un container PostgreSQL 18 avec withInitScript.

⚠️ Dette technique:

  • 😬 Contournement partiel du nouveau support Spring Boot 4 + Testcontainers (annotations @ServiceConnection) au profit d’un DataSource manuel dans un @TestConfiguration.
    → À revoir plus tard pour simplifier et utiliser la config auto Spring Boot.

🗃️ Flyway et PostgreSQL 18

Problème rencontré

  • Erreur initiale:
    • Unsupported Database: PostgreSQL 18.0
  • Cause:
    • Support modulaire Flyway pour PostgreSQL (nécessité de flyway-database-postgresql) + configuration d’URL/user/mot de passe non alignée avec le container.

Solution mise en place

  • Retour aux dépendances Flyway “classiques” + ajout du module PostgreSQL:
    • flyway-core
    • flyway-database-postgresql
  • Désactivation de l’auto-config Spring Boot pour Flyway dans les tests et:
    • Création d’un bean Flyway explicite dans TestDatabaseConfig:
      • branché sur le DataSource du container.
      • locations("classpath:migration/main")
      • baselineOnMigrate(true)
      • injection explicite des placeholders (ex: publicRoleId).

👍 Points positifs:

  • Scripts main + scripts schémas secondaires (via MigrateService) exécutés correctement sur PostgreSQL 18 en Testcontainers.
  • Contrôle total sur l’ordre et la base cible des migrations.

⚠️ Dette technique:

  • 😬 On contourne l’auto-config Flyway de Spring Boot 4 (propriétés spring.flyway.*) pour les tests:
    • Bean Flyway “manuel” spécifique aux tests.
    • Placeholders gérés à la main dans ce bean.
  • Idée pour plus tard:
    • Revoir l’usage de spring.flyway.* + @ServiceConnection pour s’appuyer davantage sur l’auto-config.

🔐 Sécurité (Spring Security 7)

Configuration HTTP et filtres

  • SecurityFilterChain modernisé avec la syntaxe lambda (authorizeHttpRequests, etc.).
  • AuthorizationFilter toujours injecté et ajouté via:
    • .addFilterAfter(authorizationFilter, BasicAuthenticationFilter.class).

Problèmes rencontrés:

  • 😵 Le filtre était bien instancié mais jamais appelé dans certains tests:
    • MockMvc n’était pas configuré avec Spring Security.
    • Un mapping path ({nameOrId}œ/rightsRequest) contenait un caractère parasite (œ), générant un 405 inattendu.

Solutions:

  • Construction de MockMvc via:
    • MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
  • Correction du mapping:
    • "/applications/{nameOrId}/rightsRequest" (suppression du œ).
  • Vérification explicite par logs que AuthorizationFilter est bien invoqué.

⚠️ Point d’attention conceptuel:

  • Endpoints comme /api/v1/login sont protégés côté HttpSecurity, mais la logique d’auth est dans un filtre custom.
  • Bien séparer:
    • ce qui doit être permitAll() pour laisser le filtre faire l’auth,
    • de ce qui doit être vraiment protégé en amont.

🌐 Web / MockMvc / Tests d’intégration

Factoring des tests

  • Création d’une classe de base:
    • AbstractIntegrationTest:
      • @SpringBootTest(classes = {OreSiNg.class, TestDatabaseConfig.class})
      • @ActiveProfiles("testmail")
      • @TestPropertySource("classpath:/application-tests.properties")
      • @DirtiesContext(BEFORE_EACH_TEST_METHOD)
      • Construction de MockMvc + injection des dépendances partagées (JsonRowMapper, UserRepository, AuthenticationService, etc.).
  • Les tests d’intégration concrets héritent maintenant de AbstractIntegrationTest:
    • réduction du boilerplate,
    • configuration homogène.

👍 Bon point:

  • Tests d’intégration plus lisibles et cohérents.
  • Moins de duplication d’annotations et d’initialisation de fixtures.

⚠️ Dette technique potentielle:

  • Couplage fort des tests à une configuration unique (AbstractIntegrationTest).
    • Penser à introduire des variantes si d’autres profils/envs de tests apparaissent (ex: sans sécurité, sans DB, etc.).

🧩 JSON, Jackson et JsonRowMapper

Problèmes de désérialisation rencontrés

  • HttpMessageConversionException sur LocalDateTimeRange.
  • Problème de boolean primitif (setted) non nullable:
    • Cannot map null into type boolean.

Causes:

  • Spring MVC désérialisait directement certains @RequestBody vers des types contenant:
    • LocalDateTimeRange,
    • des sealed interfaces,
    • des booléens primitifs.
  • Or l’application s’appuie historiquement sur un JsonRowMapper custom:
    • ObjectMapper configuré avec:
      • modules spécifiques (LocalDateTimeRange, date/time, sealed types, etc.),
      • DeserializationProblemHandler custom.

Solutions:

  • Pour les endpoints critiques (ex: /applications/{nameOrId}/authorization):
    • passage du @RequestBody à String body,
    • désérialisation via JsonRowMapper.readValue(body, CreateAuthorizationRequest.class).
  • Pour les champs booléens:
    • passage de booleanBoolean lorsque null est possible (ou laissé tel quel quand la valeur est garantie).

👍 Effet:

  • Cohérence avec le design existant: le binding complexe JSON ↔️ domaine passe toujours par JsonRowMapper.
  • Spring MVC ne tente plus de désérialiser des types qu’il ne connaît pas.

⚠️ Dette technique:

  • 😬 Reliance forte à un ObjectMapper custom central (JsonRowMapper) non réutilisé automatiquement par Spring MVC.
    • Piste d’amélioration:
      • exposer jsonRowMapper.getJsonMapper() comme @Primary ObjectMapper global et vérifier que tous les MappingJackson2HttpMessageConverter l’utilisent,
      • ou documenter clairement que tous les endpoints manipulant des types complexes doivent prendre un String/JsonNode et passer par JsonRowMapper.

🧪 Testcontainers, DB de test et Flyway

Stratégie actuelle

  • TestDatabaseConfig:
    • PostgreSQLContainer<>("postgres:18.0")
    • .withInitScript("migration/openadom_user.sql") pour créer l’utilisateur technique.
    • Bean DataSource explicitement configuré avec l’URL du container.
    • Bean Flyway dédié, avec:
      • locations("classpath:migration/main")
      • placeholders injectés.
  • MigrateService continue à gérer les migrations par schéma applicatif après les migrations “main”.

👍 Résultat:

  • Base de test représentative (PostgreSQL 18).
  • Migrations maîtrisées.

⚠️ Dette technique:

  • 😬 Double config DB/Flyway: auto-config Spring Boot en prod vs. bean manuel en tests.
    • À moyen terme, envisager de:
      • utiliser @ServiceConnection et spring.flyway.* pour les tests,
      • ou assumer officiellement que Flyway sera toujours piloté manuellement (doc interne).

🧹 Modifications “à la marge” et dettes repérées

Ajustements cosmétiques ou “lint”

  • Renommages sans impact fonctionnel:
    • Ex: binaryfiledatasetbinaryFiledataset dans BinaryFileInfos.
  • 🧹 Suppressions d’imports non utilisés (ex: DataRepositoryForBuffer).

Ces changements sont sains, mais à documenter comme “cosmétique” pour ne pas les confondre avec de vrais impacts migration.

Contournements / patches spécifiques à Spring 4

  • ⚠️ Bean ObjectMapper ajouté dans OreSiNg (ou autre config) pour essayer de propager JsonRowMapper:

    • À valider / stabiliser:
      • soit l’assumer comme convention standard,
      • soit le retirer si finalement non utilisé par Spring MVC.
  • ⚠️ Changements sur les endpoints (GET/POST/mapping) pour contourner des erreurs de mapping ou de sécurité:

    • ex: correction du path de rightsRequest,
    • attention à ne pas masquer un vrai changement de requirement.

Synthèse visuelle

  • Migration core Spring Boot 3.5.0 → 4.0.0 (parent, BOM, starters principaux).

  • Tests d’intégration 100% verts (après adaptation des tests et des filtres).

  • Flyway opérationnel avec PostgreSQL 18 + Testcontainers.

  • Refactoring des tests d’intégration via AbstractIntegrationTest.

  • 😬 Points à revisiter:

    • Intégration plus “standard” de Testcontainers avec @ServiceConnection.
    • Utilisation systématique de JsonRowMapper vs. configuration d’un ObjectMapper global partagé.
    • Allègement des beans “manuels” (Flyway, ObjectMapper) lorsqu’un starter Spring peut faire le travail.
    • Clarification des responsabilités sécurité (endpoints permitAll() vs. filtrage dans AuthorizationFilter).

Ce rapport peut servir de base:

  • à une checklist interne “Migration Spring Boot 3.x → 4.x”,
  • et à une revue de dettes techniques pour des tickets de refactor ultérieurs.