web-dev-qa-db-fra.com

Le fractionnement d'une application potentiellement monolithique en plusieurs applications plus petites permet-il d'éviter les bogues?

Une autre façon de le demander est; pourquoi les programmes ont-ils tendance à être monolithiques?

Je pense à quelque chose comme un package d'animation comme Maya, que les gens utilisent pour différents workflows différents.

Si les capacités d'animation et de modélisation étaient divisées en leur propre application distincte et développées séparément, avec des fichiers transmis entre elles, ne seraient-elles pas plus faciles à maintenir?

48
dnv

Oui. En général, deux petites applications moins complexes sont beaucoup plus faciles à entretenir qu'une seule grande.

Cependant, vous obtenez un nouveau type de bogue lorsque les applications fonctionnent toutes ensemble pour atteindre un objectif. Afin de les faire travailler ensemble, ils doivent échanger des messages et cela orchestration peut mal tourner de différentes manières, même si chaque application peut fonctionner parfaitement. Avoir un million de petites applications a ses propres problèmes particuliers.

Une application monolithique est vraiment l'option par défaut avec laquelle vous vous retrouvez lorsque vous ajoutez de plus en plus de fonctionnalités à une seule application. C'est l'approche la plus simple lorsque vous considérez chaque fonctionnalité séparément. Ce n'est qu'une fois qu'il est devenu grand que vous pouvez regarder l'ensemble et dire "vous savez quoi, cela fonctionnerait mieux si nous séparions X et Y".

94
Ewan

Le fractionnement d'une application potentiellement monolithique en plusieurs applications plus petites permet-il d'éviter les bogues

Les choses sont rarement aussi simples en réalité.

Le fractionnement n'aide certainement pas à éviter ces bogues en premier lieu. Cela peut parfois aider à trouver des bogues plus rapidement. Une application qui se compose de petits composants isolés peut permettre des tests plus individuels (genre de "unités" -) pour ces composants, ce qui peut parfois faciliter la détection de la cause première de certains bogues, et ainsi lui permettre de les corriger plus rapidement.

Cependant,

  • même une application qui semble être monolithique de l'extérieur peut se composer de beaucoup de composants testables par unité à l'intérieur, donc les tests unitaires ne sont pas nécessairement plus difficiles pour une application monolithique

  • comme Ewan l'a déjà mentionné, l'interaction de plusieurs composants introduit des risques et des bugs supplémentaires. Et le débogage d'un système d'application avec une communication interprocessus complexe peut être beaucoup plus difficile que le débogage d'une application à processus unique

Cela dépend également beaucoup de la capacité d'une application plus grande à se diviser en composants, de la largeur des interfaces entre les composants et de la façon dont ces interfaces sont utilisées.

En bref, c'est souvent un compromis, et rien où une réponse "oui" ou "non" est correcte en général.

pourquoi les programmes ont-ils tendance à être monolithiques

Est-ce qu'ils? Regardez autour de vous, il y a des millions d'applications Web dans le monde qui ne me semblent pas très monolithiques, bien au contraire. Il existe également de nombreux programmes disponibles qui fournissent un modèle de plugin (AFAIK même le logiciel Maya que vous avez mentionné le fait).

ne seraient-ils pas plus faciles à entretenir

La "maintenance plus facile" ici vient souvent du fait que différentes parties d'une application peuvent être développées plus facilement par différentes équipes, donc une meilleure charge de travail répartie, des équipes spécialisées avec une orientation plus claire, etc.

51
Doc Brown

Je devrai être en désaccord avec la majorité sur celui-ci. Le fractionnement d'une application en deux applications distinctes ne facilite pas en soi la maintenance ou le raisonnement du code.

La séparation du code en deux exécutables modifie simplement la structure physique du code, mais ce n'est pas ce qui est important. Ce qui décide de la complexité d'une application est de savoir comment étroitement couplé les différentes parties qui la composent. Ce n'est pas une propriété physique, mais une logique une.

Vous pouvez avoir une application monolithique qui a une séparation claire des différentes préoccupations et des interfaces simples. Vous pouvez avoir une architecture de microservices qui repose sur les détails d'implémentation d'autres microservices et est étroitement couplée à toutes les autres.

Ce qui est vrai, c'est que le processus de division d'une grande application en plus petites est très utile lorsque vous essayez d'établir des interfaces et des exigences claires pour chaque partie. Dans DDD parler, cela reviendrait à vos contextes bornés. Mais que vous créiez ensuite de nombreuses applications minuscules ou une grande qui ait la même structure logique est plus une décision technique.

38
Voo

Plus facile à entretenir une fois que vous avez fini de les séparer, oui. Mais les diviser n'est pas toujours facile. Essayer de séparer un morceau d'un programme en une bibliothèque réutilisable révèle où les développeurs originaux n'ont pas réussi à penser où les coutures devraient être. Si une partie de l'application atteint profondément une autre partie de l'application, il peut être difficile à corriger. Extraire les coutures vous oblige à définir plus clairement les API internes, et c'est ce qui rend finalement la base de code plus facile à maintenir. La réutilisabilité et la maintenabilité sont les deux produits de coutures bien définies.

15
StackOverthrow

Il est important de se rappeler que la corrélation n'est pas un lien de causalité.

Construire un grand monolithe puis le diviser en plusieurs petites parties peut ou non conduire à une bonne conception. (Il peut améliorer la conception, mais ce n'est pas garanti.)

Mais une bonne conception conduit souvent à la construction d'un système en plusieurs petites pièces plutôt qu'en un grand monolithe. (Un monolithe peut être le meilleur design, il est beaucoup moins probable.)

Pourquoi les petites pièces sont-elles meilleures? Parce qu'ils sont plus faciles à raisonner. Et s'il est facile de raisonner sur l'exactitude, vous êtes plus susceptible d'obtenir un résultat correct.

Pour citer C.A.R. Hoare:

Il existe deux façons de construire une conception logicielle: l'une consiste à la rendre si simple qu'il n'y a évidemment aucune lacune, et l'autre consiste à la rendre si compliquée qu'il n'y a pas évidentes déficiences.

Si tel est le cas, pourquoi quelqu'un construirait-il une solution inutilement compliquée ou monolithique? Hoare fournit la réponse dans la toute prochaine phrase:

La première méthode est bien plus difficile.

Et plus tard dans la même source (la conférence du prix Turing de 1980):

Le prix de la fiabilité est la poursuite de la plus grande simplicité. C'est un prix que les très riches trouvent le plus difficile à payer.

13
Daniel Pryden

Ce n'est pas une question avec une réponse oui ou non. La question n'est pas seulement la facilité d'entretien, c'est aussi une question d'utilisation efficace des compétences.

Généralement, une application monolithique bien écrite est efficace. La communication entre processus et entre appareils n'est pas bon marché. La rupture d'un seul processus diminue l'efficacité. Cependant, tout exécuter sur un seul processeur peut surcharger le processeur et ralentir les performances. Il s'agit du problème d'évolutivité de base. Lorsque le réseau entre en scène, le problème se complique.

Une application monolithique bien écrite qui peut fonctionner efficacement en tant que processus unique sur un seul serveur peut être facile à entretenir et à conserver sans défauts, mais ne constitue pas toujours une utilisation efficace des compétences de codage et d'architecture. La première étape consiste à diviser le processus en bibliothèques qui s'exécutent toujours comme le même processus, mais sont codées indépendamment, en suivant des disciplines de cohésion et de couplage lâche. Un bon travail à ce niveau améliore la maintenabilité et affecte rarement les performances.

L'étape suivante consiste à diviser le monolithe en processus séparés. C'est plus difficile car vous entrez en territoire difficile. Il est facile d'introduire des erreurs de conditions de concurrence. Les frais généraux de communication augmentent et vous devez faire attention aux "interfaces bavardes". Les récompenses sont grandes parce que vous brisez une barrière d'évolutivité, mais le potentiel de défauts augmente également. Les applications multi-processus sont plus faciles à maintenir au niveau du module, mais le système global est plus compliqué et plus difficile à dépanner. Les corrections peuvent être diaboliquement compliquées.

Lorsque les processus sont distribués sur des serveurs séparés ou sur une implémentation de style cloud, les problèmes deviennent plus difficiles et les récompenses plus importantes. L'évolutivité monte en flèche. (Si vous envisagez une implémentation cloud qui ne produit pas d'évolutivité, réfléchissez bien.) Mais les problèmes qui surviennent à ce stade peuvent être incroyablement difficiles à identifier et à réfléchir.

6
MarvW

Non. il ne facilite pas l'entretien. Si quoi que ce soit bienvenue à plus de problèmes.

Pourquoi?

  • Les programmes ne sont pas orthogonaux, ils doivent se préserver mutuellement dans la mesure du raisonnable, ce qui implique une compréhension commune.
  • Une grande partie du code des deux programmes est identique. Gérez-vous une bibliothèque partagée commune ou conservez-vous deux copies distinctes?
  • Vous avez maintenant deux équipes de développement. Comment communiquent-ils?
  • Vous avez maintenant deux produits qui nécessitent:

    • un style d'interface utilisateur commun, des mécanismes d'interaction, etc ... Vous avez donc maintenant des problèmes de conception. (Comment les équipes de développement communiquent-elles à nouveau?)
    • compatibilité descendante (le modélisateur v1 peut-il être importé dans animator v3?)
    • l'intégration cloud/réseau (si c'est une fonctionnalité) doit désormais être mise à jour sur deux fois plus de produits.
  • Vous avez maintenant trois marchés de consommation: les modélisateurs, les animateurs et les modélisateurs animateurs

    • Ils auront des priorités contradictoires
    • Ils auront des besoins d'assistance contradictoires
    • Ils auront des styles d'utilisation conflictuels
  • Les modélisateurs doivent-ils ouvrir deux applications distinctes pour travailler sur le même fichier? Existe-t-il une troisième application avec les deux fonctions, une application charge-t-elle les fonctions de l'autre?
  • etc...

Cela étant dit, des bases de code plus petites sont plus faciles à maintenir au niveau de l'application, vous n'allez tout simplement pas obtenir un déjeuner gratuit. C'est le même problème au cœur de Micro-Service/Any-Modular-Architecture. Ce n'est pas une panacée, les difficultés de maintenance au niveau de l'application sont échangées contre des difficultés de maintenance au niveau de l'orchestration. Ces problèmes sont toujours des problèmes, ils ne sont tout simplement plus dans la base de code, ils devront être soit évités, soit résolus.

Si la résolution du problème au niveau de l'orchestration est plus simple que sa résolution à chaque niveau d'application, il est logique de le diviser en deux bases de code et de traiter les problèmes d'orchestration.

Sinon non, ne le faites pas, vous seriez mieux servi en améliorant la modularité interne de l'application elle-même. Transférer des sections de code dans des bibliothèques cohérentes et plus faciles à gérer que l'application agit comme un plugin. Après tout, un monolithe n'est que la couche d'orchestration d'un paysage de bibliothèque.

4
Kain0_0

Il y a eu beaucoup de bonnes réponses, mais comme il y a presque une scission morte, je vais aussi jeter mon chapeau sur le ring.

D'après mon expérience en tant qu'ingénieur logiciel, j'ai trouvé que ce n'était pas un problème simple. Cela dépend vraiment de taille, échelle et but de l'application. Les applications plus anciennes, en raison de l'inertie nécessaire pour les changer, sont généralement monolithiques car c'était une pratique courante pendant longtemps (Maya serait admissible dans cette catégorie). Je suppose que vous parlez des nouvelles applications en général.

Dans les applications suffisamment petites qui sont plus ou moins simples, le surcoût requis pour maintenir de nombreuses pièces séparées dépasse généralement l'utilité de la séparation. S'il peut être entretenu par une seule personne, il peut probablement être rendu monolithique sans causer trop de problèmes. L'exception à cette règle est lorsque vous avez de nombreuses parties différentes (un frontend, un backend, peut-être quelques couches de données entre les deux) qui sont commodément séparées (logiquement).

Dans de très grandes applications, même pour une seule préoccupation, le fractionnement est logique selon mon expérience. Vous avez l'avantage de réduire un sous-ensemble de la classe de bogues possible en échange d'autres bogues (parfois plus faciles à résoudre). En général, vous pouvez également avoir des équipes de personnes travaillant isolément, ce qui améliore la productivité. De nos jours, de nombreuses applications sont cependant divisées assez finement, parfois à leur détriment. J'ai également fait partie d'équipes où l'application a été répartie sur tellement de microservices inutilement qu'elle a introduit beaucoup de frais généraux lorsque les choses cessent de se parler. De plus, le fait d'avoir à posséder toutes les connaissances sur la façon dont chaque partie communique avec les autres parties devient beaucoup plus difficile à chaque division successive. Il y a un équilibre, et comme vous pouvez le voir par les réponses ici, la façon de le faire n'est pas très claire, et il n'y a vraiment pas de norme en place.

3
CL40

Pour les applications d'interface utilisateur, il est peu probable que le nombre total de bogues diminue, mais l'équilibre du mélange de bogues vers les problèmes causés par la communication sera modifié.

Parlant d'applications/sites d'interface utilisateur - les utilisateurs sont extrêmement peu patients et exigent un temps de réponse faible. Cela fait de tout retard de communication un bogue. En conséquence, on échangera une diminution potentielle des bogues en raison de la complexité réduite d'un seul composant avec des bogues très durs et des délais de communication inter-processus/inter-machine.

Si les unités de données traitées par le programme sont volumineuses (c'est-à-dire des images), les retards inter-processus seraient plus longs et plus difficiles à éliminer - quelque chose comme "appliquer la transformation à une image de 10 Mo" gagnera instantanément + 20 Mo de disque/réseau IO en plus de 2 conversions du format en mémoire au format serializabe et inversement. Il n'y a vraiment pas grand-chose que vous puissiez faire pour cacher le temps nécessaire à l'utilisateur.

De plus, toute communication et en particulier le disque IO est soumis aux vérifications AntiVirus/Firewall - cela ajoute inévitablement une autre couche de bogues difficiles à reproduire et encore plus de retards.

Le fractionnement du "programme" monolithique brille là où les retards de communication ne sont pas critiques ou déjà inévitables

  • traitement en masse parallélisable des informations où vous pouvez échanger de petits retards supplémentaires pour une amélioration significative des étapes individuelles (éliminant parfois le besoin de composants personnalisés en utilisant une fois sur étagère). Une petite empreinte individuelle peut vous permettre d'utiliser plusieurs machines moins chères au lieu d'une seule chère par exemple.
  • diviser les services monolithiques en micro-services moins couplés - appeler plusieurs services en parallèle au lieu d'un ne va probablement pas ajouter de retards supplémentaires (peut même réduire le temps global si chacun est plus rapide et sans dépendances)
  • déplacer les opérations que les utilisateurs s'attendent à prendre longtemps - rendre des scènes/films 3D compliqués, calculer des métriques complexes sur les données, ...
  • toutes sortes de "saisie automatique", de "vérification orthographique" et d'autres aides facultatives peuvent et sont souvent conçues pour être externes - l'exemple le plus évident est les suggestions automatiques d'URL du navigateur où votre entrée est envoyée à un service externe (moteur de recherche) tout le temps .

Notez que cela s'applique aux applications de bureau ainsi qu'aux sites Web - la partie utilisateur du programme a tendance à être "monolithique" - tout le code d'interaction utilisateur lié à une seule donnée est généralement exécuté en un seul processus (il n'est pas inhabituel de fractionner processus sur une base de données comme une page HTML ou une image mais elle est orthogonale à cette question). Même pour la plupart des sites de base avec une entrée utilisateur, vous verrez une logique de validation s'exécuter du côté client, même si le rendre côté serveur serait plus modulaire et réduirait la complexité/duplication de code.

1
Alexei Levenkov

Est-ce que cela aide à prévenir les bogues?

Prévenir? Eh bien non, pas vraiment.

  • Il aide à détecter les bugs.
    À savoir tous les bogues que vous ne saviez même pas que vous aviez, que vous n'avez découverts que lorsque vous avez essayé de diviser ce désordre en parties plus petites. Donc, d'une certaine manière, cela a empêché ces bogues d'apparaître en production - mais les bogues étaient déjà là.
  • Cela aide à réduire l'impact des bugs.
    Les bogues dans les applications monolithiques ont le potentiel de faire tomber l'ensemble du système et d'empêcher l'utilisateur d'interagir avec votre application. Si vous divisez cette application en composants, la plupart des bogues n'affecteront, par conception, qu'un seul des composants.
  • Il crée un scénario pour de nouveaux bugs .
    Si vous souhaitez conserver la même expérience utilisateur, vous devrez inclure une logique nouvelle pour que tous ces composants communiquent (via REST services, via les appels système du système d'exploitation, qu'avez-vous) afin qu'ils puissent interagir de manière transparente à partir du PDV de l'utilisateur.
    À titre d'exemple simple: votre application monolithique permet aux utilisateurs de créer un modèle et de l'animer sans quitter l'application. Vous divisez l'application en deux composants: modélisation et animation. Maintenant, vos utilisateurs doivent exporter le modèle de l'application de modélisation dans un fichier, puis trouver le fichier et l'ouvrir avec l'application d'animation ... Avouons-le, certains utilisateurs ne vont pas aimer ça, vous devez donc inclure une nouvelle logique pour le application de modélisation pour exporter le fichier et lancer automatiquement l'application d'animation et faire il ouvre le fichier. Et cette nouvelle logique, aussi simple soit-elle, peut avoir un certain nombre de bugs concernant la sérialisation des données, l'accès aux fichiers et les autorisations, les utilisateurs changeant le chemin d'installation des applications, etc.
  • C'est l'excuse parfaite pour appliquer le refactoring si nécessaire .
    . des refacteurs pour rendre le code plus propre, plus simple, plus efficace, plus résistant, plus sûr. Et ce refactoring peut, en quelque sorte, aider à prévenir les bugs. Bien sûr, vous pouvez également appliquer le même refactoring à l'application monolithique pour éviter les mêmes bogues, mais ce n'est pas le cas, car il est si monolithique que vous avez peur de toucher quelque chose dans l'interface utilisateur et de casser la logique métier ¯\_ (ツ) _/¯

Je ne dirais donc pas que vous empêchez les bogues simplement en cassant une application monolithique en composants plus petits, mais vous facilitez en effet d'atteindre un point où les bogues peuvent être plus facilement évités.

0
walen