web-dev-qa-db-fra.com

Comment créer une architecture de plug-in flexible?

Un thème récurrent dans mes travaux de développement a été l'utilisation ou la création d'une architecture de plug-in interne. Je l'ai vu approché de différentes manières: fichiers de configuration (XML, .conf, etc.), frameworks d'héritage, informations de base de données, bibliothèques, etc. Dans mon expérience:

  • Une base de données n'est pas un endroit idéal pour stocker vos informations de configuration, en particulier avec des données
  • Pour tenter cela avec une hiérarchie d'héritage, il est nécessaire de connaître le codage des plug-ins, ce qui signifie que l'architecture du plug-in n'est pas si dynamique.
  • Les fichiers de configuration fonctionnent bien pour fournir des informations simples, mais ne peuvent pas gérer des comportements plus complexes
  • Les bibliothèques semblent bien fonctionner, mais les dépendances à sens unique doivent être soigneusement créées.

En cherchant à apprendre des différentes architectures avec lesquelles j'ai travaillé, je me tourne également vers la communauté pour des suggestions. Comment avez-vous mis en œuvre une SOLID de plug-in? Quel a été votre pire échec (ou le pire que vous ayez vu))? Que feriez-vous si vous deviez implémenter un nouveau plug-in Quel est le meilleur exemple d’une bonne architecture dans quel SDK ou projet open source avec lequel vous avez travaillé?

Quelques exemples que j'ai trouvés par moi-même:

Ces exemples semblent jouer sur différentes forces linguistiques. Une bonne architecture de plugin est-elle nécessairement liée au langage? Est-il préférable d'utiliser des outils pour créer une architecture de plug-in ou de le faire sur ses propres modèles?

148
justkt

Ce n'est pas une réponse autant qu'un tas de remarques/exemples potentiellement utiles.

  • Un moyen efficace de rendre votre application extensible consiste à exposer ses éléments internes en tant que langage de script et à écrire tout le contenu de niveau supérieur dans ce langage. Cela le rend assez modifiable et pratiquement évolutif (si vos primitives sont bien choisies et implémentées). Emacs est une success story de ce genre de choses. Je préfère cela au système de plug-in de style Eclipse, car si je veux étendre les fonctionnalités, je n'ai pas à apprendre l'API ni à écrire/compiler un plug-in séparé. Je peux écrire un extrait de 3 lignes dans le tampon actuel, l’évaluer et l’utiliser. Courbe d'apprentissage très lisse et des résultats très agréables.

  • Une application que j'ai un peu étendue est Trac . Il a une architecture de composant qui dans cette situation signifie que les tâches sont déléguées à des modules qui annoncent des points d’extension. Vous pouvez ensuite implémenter d'autres composants qui s'inséreraient dans ces points et modifieraient le flux. C'est un peu la suggestion de Kalkie ci-dessus.

  • Un autre qui est bon est py.test . Il suit la philosophie "la meilleure API n'est pas une API" et repose uniquement sur l'appel des hooks à tous les niveaux. Vous pouvez remplacer ces points d'ancrage dans les fichiers/fonctions nommés conformément à une convention et modifier le comportement. Vous pouvez voir la liste des plugins sur le site pour voir avec quelle rapidité/facilité ils peuvent être implémentés.

Quelques points généraux.

  • Essayez de garder votre noyau non extensible/non modifiable par l'utilisateur aussi petit que possible. Déléguez tout ce que vous pouvez à une couche supérieure pour augmenter l'extensibilité. Moins de trucs à corriger au coeur qu'en cas de mauvais choix.
  • En lien avec le point ci-dessus, vous ne devez pas prendre trop de décisions concernant l’orientation de votre projet au départ. Implémentez le plus petit sous-ensemble nécessaire, puis commencez à écrire des plugins.
  • Si vous intégrez un langage de script, assurez-vous que c'est un langage complet dans lequel vous pouvez écrire des programmes généraux et non un langage de jouet , mais seulement pour votre application.
  • Réduisez autant que vous le pouvez. Ne vous embêtez pas avec les sous-classes, les API complexes, l'enregistrement de plug-ins, etc. Essayez de garder les choses simples afin que ce soit facile et pas seulement possible pour étendre. Cela permettra à votre API de plug-in d'être davantage utilisée et encouragera les utilisateurs finaux à écrire des plug-ins. Pas seulement les développeurs de plugins. py.test le fait bien. Eclipse pour autant que je sache, ne le fait pas .
81
Noufal Ibrahim

D'après mon expérience, il existe vraiment deux types d'architectures de plug-in.

  1. On suit le Eclipse model qui est destiné à permettre la liberté et est ouvert.
  2. L'autre nécessite généralement que les plugins suivent un narrow API _ parce que le plugin remplira une fonction spécifique.

Pour le dire différemment, l’un permet aux plugins d’accéder à votre application tandis que l’autre permet à votre application d’accéder à plugins .

La distinction est subtile, et parfois il n'y a pas de distinction ... vous voulez les deux pour votre application.

Je n'ai pas une tonne d'expérience avec Eclipse/Ouverture de votre application au modèle de plugins (l'article dans l'article de Kalkie est excellent). J'ai lu un peu sur la façon dont Eclipse fait les choses, mais rien de plus.

Yegge blog de propriétés explique un peu comment l'utilisation du modèle de propriétés permet des plugins et de l'extensibilité.

La plupart de mes travaux ont utilisé une architecture de plug-in pour permettre à mon application d'accéder à des plug-ins, par exemple des données de temps/affichage/carte, etc.

Il y a des années, je créais des usines, des gestionnaires de plug-ins et des fichiers de configuration pour gérer tout cela et me laissais déterminer quel plug-in utiliser au moment de l'exécution.

  • Maintenant, d'habitude, j'ai juste un DI framework fais la plupart de ce travail.

Je dois encore écrire des adaptateurs pour utiliser des bibliothèques tierces, mais elles ne sont généralement pas si mauvaises.

24
derivation

L'une des meilleures architectures de plug-in que j'ai vu est implémentée dans Eclipse. Au lieu d'avoir une application avec un modèle de plug-in, tout est un plug-in. L'application de base elle-même est le framework de plug-in.

http://www.Eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html

14
Patrick

J'ai déjà travaillé sur un projet qui devait être si flexible dans la manière dont chaque client pouvait configurer le système. Le seul bon concept que nous ayons trouvé consistait à lui envoyer un compilateur C #!

Si la spécification est remplie avec des mots tels que:

  • Flexible
  • Brancher
  • Personnalisable

Posez beaucoup de questions sur la manière dont vous allez soutenir le système (et comment le support sera facturé, chaque client pensant que son cas est le cas normal et qu'il ne devrait pas avoir besoin de plug-ins.), Comme dans mon expérience

Le support des clients (ou des responsables du support technique) qui écrivent des plug-ins est beaucoup plus difficile que l'architecture.

8
Ian Ringrose

Je vais décrire une technique assez simple que j'ai utilisée dans le passé. Cette approche utilise la réflexion C # pour faciliter le processus de chargement du plugin. Cette technique peut être modifiée pour s'appliquer au C++, mais vous perdez le confort de pouvoir utiliser la réflexion.

Une interface IPlugin est utilisée pour identifier les classes qui implémentent des plugins. Des méthodes sont ajoutées à l'interface pour permettre à l'application de communiquer avec le plugin. Par exemple, la méthode Init que l'application utilisera pour demander au plug-in de s'initialiser.

Pour rechercher des plugins, l'application analyse un dossier de plugins pour les assemblages .Net. Chaque assemblée est chargée. Reflection est utilisé pour rechercher les classes qui implémentent IPlugin. Une instance de chaque classe de plugin est créée.

(Un fichier XML peut également répertorier les assemblys et les classes à charger. Cela peut améliorer les performances mais je n'ai jamais trouvé de problème avec les performances).

La méthode Init est appelée pour chaque objet du plugin. Une référence à un objet qui implémente l'interface de l'application est transmise: IApplication (ou un autre nom spécifique de votre application, par exemple ITextEditorApplication).

IApplication contient des méthodes permettant au plugin de communiquer avec l'application. Par exemple, si vous écrivez un éditeur de texte, cette interface aurait une propriété OpenDocuments qui permet aux plugins d’énumérer la collection de documents actuellement ouverts.

Ce système de plug-in peut être étendu aux langages de script, par exemple Lua, en créant une classe de plug-in dérivée, par exemple LuaPlugin, qui transmet les fonctions IPlugin et l'interface de l'application à un script Lua.

Cette technique vous permet d'implémenter de manière itérative votre IPlugin, IApplication et d'autres interfaces spécifiques à l'application au cours du développement. Lorsque l'application est terminée et bien refactorisée, vous pouvez documenter vos interfaces exposées et vous devez disposer d'un système Nice pour lequel les utilisateurs peuvent écrire leurs propres plugins.

7
Ashley Davis

D'habitude j'utilise MEF. Le cadre d'extensibilité géré (ou MEF) simplifie la création d'applications extensibles. MEF offre des fonctionnalités de découverte et de composition que vous pouvez utiliser pour charger des extensions d'application.

Si vous êtes intéressé, lisez plus ...

7
BALKANGraph

D'après mon expérience, les langages de script et les bibliothèques sont les deux meilleurs moyens de créer une architecture de plug-in flexible. Ces deux concepts sont dans mon esprit orthogonaux; les deux peuvent être mélangés dans n'importe quelle proportion, plutôt que la programmation fonctionnelle et orientée objet, mais trouvent leurs plus grandes forces lorsqu'ils sont équilibrés. Une bibliothèque est généralement responsable de l'exécution d'une interface spécifique avec une fonctionnalité dynamique, alors que les scripts ont tendance à mettre l'accent sur fonctionnalité avec une interface dynamique.

J'ai trouvé qu'une architecture basée sur scripts gérant des bibliothèques semble fonctionner le mieux. Le langage de script permet une manipulation de haut niveau des bibliothèques de niveau inférieur. Les bibliothèques sont ainsi libérées de toute interface spécifique, laissant ainsi toute l’interaction au niveau de l’application entre les mains plus souples du système de script.

Pour que cela fonctionne, le système de script doit avoir une API assez robuste, avec des liaisons aux données de l'application, à la logique et à l'interface graphique, ainsi qu'aux fonctionnalités de base pour importer et exécuter du code à partir de bibliothèques. En outre, les scripts doivent généralement être sûr dans le sens où l’application peut récupérer avec élégance à partir d’un script mal écrit. L'utilisation d'un système de script comme couche d'indirection signifie que l'application peut se détacher plus facilement en cas de Something Bad ™.

Le moyen de packaging des plugins dépend en grande partie de vos préférences personnelles, mais vous ne pouvez jamais vous tromper avec une archive compressée avec une interface simple, disons PluginName.ext dans le répertoire racine.

3
Jon Purdy

Je pense que vous devez d’abord répondre à la question: "Quels composants sont censés être des plugins?" Vous voulez garder ce nombre au minimum absolu ou le nombre de combinaisons que vous devez tester explose. Essayez de séparer votre produit principal (qui ne devrait pas avoir trop de flexibilité) de la fonctionnalité du plug-in.

J'ai constaté que le principal IOC (Inversion of Control)) (lire Springframework) fonctionne bien pour fournir une base flexible, à laquelle vous pouvez ajouter une spécialisation pour faciliter le développement de plug-in.

  • Vous pouvez analyser le conteneur pour le mécanisme "interface en tant que publication de type de plugin".
  • Vous pouvez utiliser le conteneur pour injecter des dépendances communes dont les plug-ins peuvent avoir besoin (par exemple, ResourceLoaderAware ou MessageSourceAware).
3
Justin