Comment abordez-vous les conflits de dépendance transitive qui ne sont connus qu'au moment de l'exécution? [fermé]

9

Comment abordez-vous normalement les problèmes de dépendance transitive qui se produisent au moment de l'exécution dans les grands projets logiciels?

Au cours des trois dernières semaines, j'ai essayé de démarrer un composant d'un gros morceau de logiciel dans un autre composant du logiciel, mais il meurt par intermittence en raison de problèmes de dépendance transitive qui ne sont connus qu'au moment de l'exécution.

Par problèmes de dépendance transitive, je veux dire que certaines dépendances des dépendances d'un projet donné entrent en collision avec d'autres dépendances au moment de l'exécution, provoquant une instabilité ou une défaillance instantanée.

Il existe des centaines, des centaines de dépendances en cours d'utilisation et environ 50 sous-projets associés à l'outil qui sont travaillés de manière isolée par d'autres équipes, où tous les modules ont des dépendances profondément imbriquées entre elles. Personne ne sait à quoi servent tous les sous-projets, étant donné l'ampleur et la complexité du projet.

Dans ce cas, tenteriez-vous de générer une représentation visuelle du DAG pour chacune des dépendances du composant affecté et tenteriez-vous de déterminer où des collisions peuvent se produire au moment de l'exécution? Je n'ai aucun contrôle sur la façon dont les dépendances sont gérées dans d'autres sous-projets et je ne peux pas modifier le code Java écrit par d'autres développeurs

Les solutions que j'ai trouvées ne fonctionnent que pendant une heure ou deux, puis elles cessent de fonctionner en raison de changements dans les composants en amont. Un exemple de composant en amont est un artefact dont dépend le projet sur lequel je travaille et qui est construit à un stade antérieur dans le pipeline CI.


À la demande des autres , je vais inclure des informations sur la technologie utilisée, au risque de clore la question pour avoir fourni trop d'informations, ou pour le corps trop long:

  • Maven est utilisé pour la gestion des dépendances; et
  • Le ressort est utilisé comme conteneur DI;
  • La plupart des problèmes de dépendance impliquent des contextes de bean qui se chevauchent en raison du chargement des contextes d'autres modules au moment de l'exécution
  • Le produit fonctionne correctement et il existe une multitude de tests unitaires et de tests d'intégration pour échapper à l'exactitude fonctionnelle du programme

En général, je recherche une approche indépendante du langage pour identifier les moyens de résoudre les conflits de dépendance sans énumérer à travers toutes les combinaisons possibles des dépendances d'un projet donné.

Je ne peux pas ré-architecturer le projet, ajouter des portes de qualité supplémentaires, pousser pour des changements de paradigme à travers l'entreprise ou changer de langue comme résolution.

Tyler
la source
Parlez-vous de quelque chose comme le conteneur DI n'est pas correctement configuré pour savoir que pour IFoo utiliser IFooImpl?
Andy
Non, c'est plus comme si le package (b) dans le projet (b) est une dépendance transitive du package (a) dans le projet (A), mais (B) n'est introduit qu'au moment de l'exécution. Le logiciel fonctionne tout de ce que je peux dire, mais j'ai juste besoin d'un ensemble approprié de dépendances pour permettre à toutes les dépendances transitives d'être résolues correctement. Il existe des ensembles de dépendances qui permettront au produit de démarrer correctement, mais ces configurations sont très fragiles. En tant que développeur junior. Je n'ai aucun contrôle sur ça.
Tyler
2
Allez, "problème de dépendance" est un terme très générique, et tous ceux qui lisent ceci envisagent probablement quelque chose de différent. Pour les logiciels réels, cela dépend de la technologie, veuillez donc donner un exemple réel pour une technologie spécifique que vous avez en tête. Les problèmes surviennent-ils parce que les composants nécessaires sont manquants? Ou parce qu'une mauvaise version est fournie? Quel type de mécanisme de résolution des dépendances est déjà en place? Précisez s'il vous plaît.
Doc Brown
1
Dans le monde .Net, je résous ce problème en installant simplement les packages dans mon projet d'application principal. Bien qu'aucun code d'application n'utilise réellement les bibliothèques de plugins, le compilateur .Net garantit que la DLL est au bon endroit pendant la génération et donc le déploiement.
Andy
2
"Je n'ai aucun contrôle sur la façon dont les dépendances sont gérées et je ne peux pas changer de code" - que pouvez- vous réellement changer? Si vous ne pouvez rien changer, la question semble inutile.
Doc Brown

Réponses:

6

Il peut être possible de résoudre votre problème en utilisant des chargeurs de classe personnalisés pour donner à chaque module de l'application son propre environnement d'exécution.

Fondamentalement, vous définiriez un environnement de base qui se compose d'un petit noyau de l'application, avec toutes les dépendances universellement acceptées et toutes les interfaces nécessaires pour que les modules communiquent entre eux. Ensuite, chaque module que vous chargez obtient son propre chargeur de classe qui peut accéder (1) aux classes de l'environnement d'exécution, (2) aux classes du cœur de l'application et (3) aux classes de ce module et à ses dépendances directes. Toutes les communications inter-modules passent par des interfaces définies dans le cœur de sorte qu'aucun module ne dépend directement d'un autre.

Cette technique est utilisée dans les serveurs d'applications pour permettre aux applications d'avoir des dépendances qui autrement se heurteraient, de sorte que vous pouvez voir une implémentation fonctionnelle de la technique, par exemple, dans Tomcat (et, si vous êtes chanceux, vous pourrez peut-être utiliser Tomcat's mise en œuvre avec peu de changement).

Jules
la source
1
Je ne suis pas autorisé à ré-architecturer le projet pour résoudre le problème de dépendance. Passer à une architecture de type micro-noyau serait cependant plutôt cool.
Tyler
2
@Tyler: néanmoins je pense que c'est une bonne réponse générique à la partie générique de la question.
Doc Brown
1
Cela ressemble un peu à osgi.
SpaceTrucker
4

Je n'ai jamais travaillé avec Maven ou Spring, mais pour vous donner une réponse générique à une question générique: quand il s'agit de problèmes de dépendance, votre système doit être conçu pour les détecter le plus tôt possible et quand ces problèmes sont détectés , ils doivent être signalés immédiatement, y compris leur cause possible.

Idéalement, ce point dans le temps n'est pas au "temps d'exécution de la production". Le meilleur moment est le «temps de construction», mais cela semble impossible dans votre cas. La deuxième meilleure option est donc les tests d'exécution. Vous nous avez dit qu'il existe des «tests d'intégration», mais comme ils ne détectent pas les problèmes de dépendance, ils semblent incomplets ou ne testent pas les collisions de dépendance en général. C'est là que vous devriez commencer à essayer de résoudre le problème.

De plus, pour rendre les tests plus efficaces, vos composants doivent "échouer rapidement" lorsqu'un problème de dépendance survient. Cela signifie que dès qu'une dépendance est résolue et qu'un composant est chargé, les collisions potentielles de dépendance doivent être vérifiées et signalées immédiatement. Si le système fonctionne pour la première fois pendant une heure avant qu'une collision ne soit détectée, il devient très difficile d'identifier la cause du problème. Je ne sais pas si Maven / Spring vous propose déjà une stratégie intégrée de "échec rapide", mais si ce n'est pas le cas, essayez de penser dans cette direction.

Pour atténuer les problèmes de production, votre système doit être conçu pour ne pas mourir complètement lorsqu'une collision de dépendance se produit avec un sous-composant moins important impliqué. L'échec ne doit pas être masqué, bien sûr, mais le système devrait idéalement rejeter le chargement de ce composant, consigner et / ou signaler le problème et rester dans un état stable. Lorsque le composant est chargé en premier, le système devient instable et vous ne détectez le problème qu'après, vous devrez probablement arrêter et redémarrer complètement le système, ce que je suppose que vous devriez éviter.

Par exemple, si votre système est une boutique Web et que votre sous-module pour "abonnement à la newsletter" ne peut pas être chargé pendant quelques heures, c'est quelque chose qui pourrait être plus facilement toléré comme si la boutique entière ne fonctionnait plus.

Enfin, cela pourrait ne pas être une option dans votre cas, mais si les composants peuvent être exécutés de manière plus isolée les uns par rapport aux autres, et si cela peut éviter les collisions de dépendance, cela peut être une approche qui mérite réflexion.

Doc Brown
la source
2

Considérez cela comme si je réfléchissais tout haut ici. Selon les exigences / contraintes de temps, je pourrais considérer ceci:

1) créer une liste d'interfaces et de leurs implémentations (en utilisant la réflexion, si disponible)

2) créez une sorte de wrapper autour de votre DI. Lorsqu'une DI est invitée à fournir une implémentation concrète, voyez s'il y a déjà une règle DI en place; si la règle existe - renvoyez simplement ce que votre DI fournit; sinon, s'il y a une seule implémentation d'une interface et que vous pouvez créer une instance - log, et renvoyer une instance; sinon connectez-vous et échouez.

Cela dépend de la façon dont vous voulez vous impliquer, je suppose, mais finalement vous voulez juste avoir une liste complète de ce qui nécessite quoi - cette solution finira par générer cette liste.

Cependant, cela représente sans doute beaucoup de travail et n'est pas fiable - et si vous avez une condition étrange nécessitant une certaine dépendance une fois dans une lune bleue?

Que voulez-vous dire lorsque vous dites «changements dans les composants en amont»?

Evgeni
la source
Désolé, je ne voulais pas fournir trop d'informations de fond - lorsque je décris l'environnement d'exploitation, les questions sont généralement sous-évaluées et flambées à mort. Je mettrai à jour la question d'origine avec une section de définitions.
Tyler
2

Si je vous suis correctement, le problème est que le code que vous devez utiliser a des dépendances d'exécution sur les modules exprimés en Spring XML, mais maven n'en est pas informé. Ainsi, lorsque vous les utilisez, les choses dont ils ont besoin (les dépendances transitives) ne sont pas sur votre chemin de classe, et ils ne font pas ce qu'ils devraient.

Je suppose que lorsque vous demandez à vos collègues «comment puis-je faire fonctionner cela?», Ils disent «évidemment, vous avez besoin de cette liste de 35 modules et versions , comment pourriez-vous ne pas le savoir?

Vraisemblablement lorsque le test d'intégration est terminé, les modules requis sont déclarés comme dépendances de test. La solution systématique consiste donc à mettre en place des inspections des pom de test d'intégration pour s'assurer qu'ils n'ont rien d'autre que des outils de test tiers déclarés comme dépendances de test. Cela pourrait même être automatisé par le plugin mavenforcer .

Apparemment, vous ne pouvez pas faire cela, donc votre meilleure option se trouve probablement ici .

Soru
la source
C'est exactement ce qui se passe, d'après ce que je peux dire. Jusqu'à présent, je jongle avec 30 dépendances et je manipule les étendues pour éliminer les collisions, mais l'examen par les pairs est ce qui est nécessaire ici.
Tyler