Bilan de projet : Premières expériences de développement Java et Python

Mon premier projet où j'ai eu la responsabilité d'un module touche à sa fin. Je souhaite tirer des leçons des problèmes rencontrés afin de les éviter à l'avenir.

Le projet comprenait une partie back-end développée en Java et une partie application mobile en Python. La communication entre les back-ends était assurée par OpenFeign. J'ai principalement travaillé sur le code back-end.

Pour illustrer mes apprentissages, je vais prendre l'exemple d'un système de gestion d'étudiants.

Problèmes rencontrés lors du dévelopement

1. Redondance et confusion des champs

Au début de la création des tables, un manque d'uniformité dans le style de nommage, probablement dû à plusieurs contributeurs, a engendré des problèmes.

  • Clés étrangères logiques incohérentes : L'identifiant d'un étudiant (id dans la table student) était référencé différemment dans d'autres tables : studentId dans class et student dans team.
  • Champs redondants : En raison de la communication entre deux interfaces utilisant des bases de données distinctes (SQLite pour l'application mobile et PostgreSQL pour l'application web), nous avons introduit des champs redondants pour faciliter la synchronisation des données. L'entité student possédait id et sync_id. Durant le développement, la confusion entre ces deux champs a conduit à la nécessité de faire correspondre id et sync_id lors de la création de nouvelles données.

Ces problèmes pourraient être résolus en :

  • Unifiant les conventions de nommage des tables et des champs.
  • Clarifiant l'utilisation des champs redondants : si sync_id est nécessaire pour la synchronisation, il devrait être utilisé comme clé principale pour les requêtes et la synchronisation, tandis que id assurerait uniquement l'unicité lors de l'insertion.

2. Caractères non-ASCII, énumérations et identifiants

Ce problème découle d'un manque de rigueur et de la pression du temps.

  • Stockage de valeurs textuelles : Pour les notes d'examen des étudiants, au lieu de stocker une valeur textuelle comme 'Excellant', il aurait été préférable d'utiliser une clé associée à une énumération ou un dictionnaire (par exemple, 1 pour 'Excellent', 2 pour 'Bien').
  • Gestion des énumérations pour le genre : Initialement, nous avons utilisé des types énumérés pour le champ genre afin de faciliter l'import/export avec Excel (via le convertisseur EasyExcel). Cependant, JPA stocke par défaut l'index ordinal de l'énumération plutôt que sa valeur définie. Nous avions configuré des paires clé-valeur comme {clé=1, valeur="Homme"}, {clé=2, valeur="Femme"}. La base de données a donc stocké 0 pour "Homme" et 1 pour "Femme". Ceci a causé des incohérences ultérieures, notamment lors de la classification des participants par genre pour des cours de sport (groupe mixte : 0, groupe masculin : 1, groupe féminin : 2).

Pour remédier à cela lors de l'utilisation d'énumérations avec JPA, il est préférable d'utiliser l'annotation @Enumerated(EnumType.STRING) pour stocker le nom de l'énumération (par exemple, "HOMME", "FEMME") :


@Enumerated(EnumType.STRING) // Stocke le nom de l'énumération "MALE", "FEMALE"
 

3. Gestion des branches Git

Au début du projet, après la mise en place de l'architecture, j'étais le principal contributeur et j'utilisais majoritairement une branche nommée test. Par la suite, avec l'arrivée d'autres développeurs pour implémenter des fonctionnalités, le manque d'une stratégie de gestion de branches claire a entraîné des risques de perte de code et de désorganisation. Une pratique rigoureuse de "une personne, une branche" suivie d'une fusion vers la branche principale aurait été préférable.

Mise à jour : Plus tard, malgré l'utilisation de plusieurs branches, mon code a été écrasé sans notification dans Git, ce qui était très frustrant. Un collègue a expliqué que le problème ne venait pas de la gestion des branches elle-même, mais d'une mauvaise résolution des conflits lors de la fusion, conduisant à la perte de modifications. Ces pertes peuvent être récupérées via l'historique Git, notamment dans l'IDE, qui enregistre les modifications apportées lors des fusions.

4. Utilisation de jsonb dans PostgreSQL

J'étais responsable de la conception et du développement d'un module pour gérer les examens, incluant la définition des seuils de notation et des détails sur les pénalités. Ma conception initiale reposait sur trois tables : exam_rules, exam_rule_details (clé étrangère vers exam_rules) et deduction_items (clé étrangère vers exam_rules). Lors de la création d'une règle d'examen, le front-end devait soumettre les informations pour ces trois tables séquentiellement. Cette approche impliquait trois opérations de mise à jour distinctes de la base de données, compromettant l'atomicité des transactions. Si la création d'une règle échouait après la création réussie de la règle principale, il fallait une logique de suppression supplémentaire côté front-end pour maintenir la cohérence.

L'approche de mise à jour présentait des inconvénients encore plus importants : une mise à jour réussie de la règle principale suivie d'un échec dans la mise à jour des détails rendait le rollback complexe. Les ajouts, suppressions et modifications des détails et des pénalités nécessitaient une logique de gestion des modifications complexe côté front-end.

Après plusieurs semaines de développement, le front-end a proposé d'intégrer directement les détails et les pénalités dans un champ extra de la table principale, éliminant le besoin de tables séparées. Manquant d'expérience avec jsonb dans PostgreSQL, j'ai dû accepter cette solution et travailler en collaboration avec le front-end.

Avantages de jsonb :

  • Atomicité : Une seule opération sur une table garantit l'atomicité des transactions.
  • Opérations atomiques de mise à jour : Simplifie la gestion des mises à jour et réduit la charge de travail front-end/back-end.
  • Flexibilité des requêtes : jsonb offre des capacités de requête puissantes.

Exemple de requête :


SELECT * FROM exam_rules
WHERE rule_config -> 'deduction_items' @> '[{"item_code": "LATE"}]';
 
  • Opérateur -> : Extrait la valeur d'une clé spécifique d'un objet JSONB.
  • Opérateur @> : Vérifie si le JSONB de gauche contient le JSONB de droite.

5. Couplage du code

En raison de ma méconnaissance du langage Python et du manque de temps, une partie du code a été développée avec l'aide de mon collègue Trae, qui privilégiait la complétion des fonctionnalités au respect de l'architecture du projet. Cela a conduit à un mélange d'entités dans un même service et à la création répétée de fonctions pour lire des JSON, rendant le code moins lisible et plus volumineux. Bien que j'aie identifié ce problème plus tard, il a fallu le corriger en séparant les classes utilitaires et en découplant les services. J'espère être plus vigilant à l'avenir lors de l'utilisation de langages inconnus.

6. Vision globale du code

Mon contrôle sur le code était insuffisant, particulièrement sur le projet Python. Lors de modifications, j'oubliais souvent de synchroniser les interfaces de requête et de mise à jour, entraînant des problèmes lors de la phase de développement avec le front-end. Ce manque d'expérience globale et la difficulté avec le langage ont augmenté l'entropie du projet. La communication de données via OpenFeign entre les back-ends Java et Python a également été source de nombreux problèmes, notamment en raison des différences entre leurs bases de données et des types de données. Chaque modification de table dans une partie nécessitait une mise à jour correspondante dans l'autre, ralentissant considérablement le développement. Les commits fréquents pour synchroniser les données étaient monnaie courante.

Problèmes rencontrés lors du déplacement

Une fois le développement du code terminé, j'ai dû gérer le déploiement du projet pour permettre au client de l'utiliser tout en continuant son développement. Ce processus, qui a duré environ deux semaines, a mis en lumière plusieurs problèmes.

1. Déploiement de projet avec Nginx et Docker

Mon manque de familiarité avec Nginx a rendu la configuration difficile. N'ayant qu'une connaissance théorique superficielle, je ne pouvais pas résoudre les problèmes de configuration. J'ai dû dépendre de Trae pour générer les fichiers de configuration, ce qui a entraîné de légers retards dans le développement et les tests. J'ai ensuite consacré du temps à l'étude de Nginx pour acquérir les bases nécessaires. Ma compréhension de Docker était également limitée aux commandes de base comme docker ps, docker pull, et docker push. Le déploiement local de multiples composants (MQ, PostgreSQL, MinIO, Redis) s'est avéré complexe sur Windows. J'ai néanmoins réussi à déployer MinIO, Redis et PostgreSQL localement. Pour améliorer mes compétences, j'ai utilisé Docker Desktop et Docker Compose sur Windows pour déployer l'ensemble des composants. Le manque de compréhension du montage de volumes (volume mounting) a été un obstacle majeur. Bien que j'aie réussi à monter le fichier nginx.conf dans Docker, le dossier de distribution (dist) du front-end n'était pas directement accessible. Un collègue m'a rappelé que le répertoire html de Nginx devait également être monté. J'avais initialement négligé l'importance des fichiers de configuration distincts pour les environnements de développement (dev) et de production (prod), ce qui a compliqué mon processus de déploiement.

2. Inconvénients du développement à distance

En tant que représentant du back-end lors du déplacement, j'ai eu du mal à transmettre des informations précises à mes collègues développeurs back-end, ce qui a conduit à des divergences de données. À l'approche de la fin du projet, en plus de mes propres tâches de développement, de déploiement et de test, j'ai dû réviser le code de mes collègues. Certains d'entre eux commettaient du code généré par IA sans l'examiner attentivement, tandis que d'autres commettaient des suppressions de fonctions sans vérification, ce qui a ralenti le projet. La majeure partie des corrections ultérieures est devenue ma responsabilité.

3. Méconnaissance des réseaux informatiques et du Raspberry Pi

Le projet impliquait une synergie entre plusieurs logiciels : une application web et une application mobile nécessitant une connexion à un Raspberry Pi. Le client souhaitait que le projet fonctionne dans un environnement réseau isolé (que j'ai mis en place, utilisant le Raspberry Pi comme switch). Les problèmes de connectivité réseau ont été une source de difficultés majeures. Mes connaissances en réseaux informatiques, acquises durant mes études, étaient insuffisantes (j'avais du mal à me souvenir des 7 couches du modèle OSI, et des termes WAN/LAN). L'installation de l'ensemble du projet a donc été compliquée.

  • La carte SD du Raspberry Pi est fragile ; un arrêt inattendu peut la rendre inutilisable.
  • Le Raspberry Pi utilise des vis de différentes tailles, ce qui rend le montage et le démontage fastidieux.
  • Le problème le plus critique est que le Raspberry Pi (agissant comme switch) perdait occasionnellement la connexion au réseau local. La cause exacte reste inconnue, et le redémarrage du Raspberry Pi était la seule solution. Il est possible que les câbles ou les ports soient fragiles.
  • Des manipulations incorrectes du module d'identification par empreintes digitales pouvaient rendre l'appareil inutilisable, nécessitant parfois de débrancher le module, mais souvent un redémarrage du Raspberry Pi était indispensable. La difficulté réside dans le fait que les modules d'empreintes digitales et les Raspberry Pi ont été achetés, et personne dans l'équipe n'a d'expertise matérielle pour optimiser ces composants.

4. Écart entre les besoins métier et le développement

Après avoir expérimenté le logiciel, le client a constaté des différences entre ses attentes et le produit développé. Par exemple, lors des tests physiques universitaires, des épreuves comme la course de 50 mètres ou de 3000 mètres impliquent un départ simultané de plusieurs participants. Lors de la conception, tant côté front-end que back-end, il était facile de les regrouper. Cependant, dans l'usage réel, notre projet ne pouvait pas remplacer une fonction de chronomètre dans toutes les situations. Pour la course de 3000 mètres, les participants sont plus espacés, permettant à chacun d'avoir son propre chronomètre gérable par le logiciel. Pour la course de 50 mètres, avec un départ groupé, attribuer un chronomètre individuel à chaque participant aurait rendu le système inutilisable.

Conclusion

Bien que ce projet ne soit pas encore livré, les problèmes rencontrés m'ont beaucoup appris : utilisation de PostgreSQL, normes de développement, utilisation de Nginx et Docker, meilleure compréhension du Raspberry Pi et de Linux, ainsi qu'une amélioration de ma maîtrise de Git. Ces enseignements constituent une récolte précieuse. J'espère que la validation finale du projet se déroulera sans encombre.

Étiquettes: Java Python PostgreSQL Git nginx

Publié le 30 juillet à 13h07