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

154

Un thème récurrent dans mon travail de développement a été l'utilisation ou la création d'une architecture plug-in en interne. Je l'ai vu abordé de nombreuses manières - fichiers de configuration (XML, .conf, etc.), cadres 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 bon endroit pour stocker vos informations de configuration, en particulier mélangées avec des données
  • Tenter cela avec une hiérarchie d'héritage nécessite des connaissances sur les plug-ins à coder, ce qui signifie que l'architecture des plug-ins 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 unidirectionnelles doivent être soigneusement créées.

Alors que je cherche à apprendre des différentes architectures avec lesquelles j'ai travaillé, je me tourne également vers la communauté pour des suggestions. Comment avez-vous implémenté une architecture de plug-in SOLID? Quel a été votre pire échec (ou le pire échec que vous ayez vu)? Que feriez-vous si vous deviez implémenter une nouvelle architecture de plug-ins? Quel SDK ou projet open source avec lequel vous avez travaillé a le meilleur exemple d'une bonne architecture?

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

Ces exemples semblent jouer sur diverses 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 plugin, ou de le faire sur ses propres modèles suivants?

justkt
la source
Pouvez-vous dire pourquoi une architecture de plugin a été un thème commun? Quels problèmes résout-il, ou quels objectifs aborde-t-il? De nombreux systèmes / applications extensibles utilisent des plugins d'une certaine forme, mais la forme varie considérablement en fonction du problème résolu.
mdma
@mdma - c'est une excellente question. Je dirais qu'il y a des objectifs communs dans un système extensible. Peut-être que les objectifs sont tout ce qui est commun, et la meilleure solution varie en fonction de l'extensibilité du système, de la langue dans laquelle le système est écrit, etc. Cependant, je vois des modèles comme IOC être appliqués dans de nombreux langages, et on m'a demandé de faire des plugins similaires (pour que les morceaux répondent aux mêmes demandes de fonctionnalités) encore et encore. Je pense qu'il est bon d'avoir une idée générale des meilleures pratiques pour différents types de plugins.
justkt

Réponses:

89

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 composants internes en tant que langage de script et à écrire tous les éléments de niveau supérieur dans ce langage. Cela le rend tout à fait modifiable et pratiquement à l'épreuve du temps (si vos primitives sont bien choisies et implémentées). Une histoire à succès de ce genre de chose est Emacs. Je préfère cela au système de plugins de style eclipse car si je veux étendre les fonctionnalités, je n'ai pas besoin d'apprendre l'API et d'écrire / compiler un plugin séparé. Je peux écrire un extrait de 3 lignes dans le tampon actuel lui-même, l'évaluer et l'utiliser. Courbe d'apprentissage très fluide et résultats très agréables.

  • Une application que j'ai un peu étendue est Trac . Il a une architecture de composants 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'intégreraient dans ces points et modifier le flux. C'est un peu comme la suggestion de Kalkie ci-dessus.

  • Un autre bon est py.test . Il suit la philosophie «la meilleure API n'est pas une API» et repose uniquement sur les appels de hooks à tous les niveaux. Vous pouvez remplacer ces hooks dans les fichiers / fonctions nommés selon 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 mis en œuvre.

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 afin que l'extensibilité augmente. Moins de trucs à corriger dans le noyau puis en cas de mauvais choix.
  • En lien avec le point ci-dessus, vous ne devriez pas prendre trop de décisions sur la direction 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 qu'il s'agit d'un langage complet dans lequel vous pouvez écrire des programmes généraux et non un langage jouet uniquement pour votre application.
  • Réduisez le plus possible le passe-partout. Ne vous embêtez pas avec les sous-classes, les API complexes, l'enregistrement de plugins et des trucs comme ça. Essayez de rester simple afin que ce soit facile et pas seulement possible d'étendre. Cela permettra à votre API de plug-in d'être davantage utilisée et encouragera les utilisateurs finaux à écrire des plugins. Pas seulement des développeurs de plugins. py.test fait bien cela. Eclipse pour autant que je sache, ne le fait pas .
Noufal Ibrahim
la source
1
Je voudrais étendre le point de langage de script pour qu'il vaut la peine d'envisager d'intégrer un langage existant comme python ou perl
Rudi
Vrai Rudi. De nombreux langages sont conçus pour être intégrés. Guile, par exemple, est conçu uniquement pour l'intégration. Cela fait gagner du temps si vous rendez votre application scriptable. Vous pouvez simplement déposer dans l'une de ces langues.
Noufal Ibrahim
24

D'après mon expérience, j'ai trouvé qu'il existe vraiment deux types d'architectures de plug-ins.

  1. On suit le Eclipse modelqui est censé permettre la liberté et est illimité.
  2. L'autre nécessite généralement que les plugins suivent un narrow APIcar le plugin remplira une fonction spécifique.

Pour indiquer cela d'une manière différente, l'un permet aux plugins d'accéder à votre application tandis que l'autre autorise votre application à accéder aux 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 le post de Kalkie est génial). J'ai lu un peu sur la façon dont Eclipse fait les choses, mais rien de plus.

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

La plupart du travail que j'ai effectué a utilisé une architecture de plugin pour permettre à mon application d'accéder à des plugins, des choses comme l'heure / l'affichage / les données cartographiques, etc.

Il y a des années, je créais des usines, des gestionnaires de plugins et des fichiers de configuration pour tout gérer et je me permettais de déterminer quel plugin utiliser au moment de l'exécution.

  • Maintenant, j'ai l'habitude de DI frameworkfaire la plupart de ce travail.

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

menjaraz
la source
@Justin oui, DI = injection de dépendances.
dérivation
14

L'une des meilleures architectures de plug-ins que j'ai vues 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

Patrick
la source
Certainement un bon exemple, mais est-il plus grand et plus complexe que ce que nécessitent de nombreuses applications?
justkt
Oui, cela pourrait être, l'augmentation de la flexibilité a un coût. Mais je pense que cela montre un point de vue différent. Pourquoi le menu de votre application ne pourrait-il pas être un plug-in ou la vue principale?
Patrick
6
La même approche (tout est un plugin) est utilisée dans la nouvelle version du framework Symfony. Ils sont appelés bundles mais le principe est le même. Son architecture est assez simple, vous devriez peut-être y jeter un coup d'œil: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld
@Marjin Huizendveld - vous devriez ajouter ceci comme réponse séparée afin que je puisse voter pour la réponse. On dirait un cadre intéressant!
justkt
11

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

Une IPlugininterface 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 Initméthode que l'application utilisera pour demander au plugin de s'initialiser.

Pour trouver des plugins, l'application scanne un dossier de plugins pour les assemblys .Net. Chaque assemblage est chargé. La réflexion est utilisée pour rechercher les classes qui implémentent IPlugin. Une instance de chaque classe de plugin est créée.

(Sinon, un fichier Xml peut répertorier les assemblys et les classes à charger. Cela peut améliorer les performances mais je n'ai jamais trouvé de problème de performances).

La Initméthode est appelée pour chaque objet plugin. Il est passé une référence à un objet qui implémente l'interface de l'application: IApplication(ou quelque chose d'autre nommé spécifique à votre application, par exemple ITextEditorApplication).

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

Ce système de plugin peut être étendu aux langages de script, par exemple Lua, en créant une classe de plugin dérivée, par exemple LuaPluginqui transmet les IPluginfonctions et l'interface de l'application à un script Lua.

Cette technique vous permet d'implémenter itérativement votre IPlugin, IApplicationet d' autres interfaces spécifiques d'application au cours du développement. Lorsque l'application est complète et bien refactorisée, vous pouvez documenter vos interfaces exposées et vous devriez avoir un système agréable pour lequel les utilisateurs peuvent écrire leurs propres plugins.

Ashley Davis
la source
7

Habituellement, j'utilise MEF. Le Managed Extensibility Framework (ou MEF en abrégé) 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 ...

BALKANGraph
la source
Merci, ça a l'air intéressant. Est-il facile d'appliquer des principes similaires dans d'autres langues?
justkt
En fait, MEF est uniquement pour le framework .NET, je ne sais rien des autres frameworks
BALKANGraph
7

Une fois, j'ai travaillé sur un projet qui devait être si flexible dans la façon dont chaque client pouvait configurer le système, dont la seule bonne conception que nous ayons trouvée était d'expédier au client un compilateur C #!

Si la spécification est remplie de mots comme:

  • Souple
  • Brancher
  • Personnalisable

Posez beaucoup de questions sur la manière dont vous soutiendrez le système (et comment l'assistance sera facturée, car chaque client pensera que son cas est le cas normal et ne devrait pas avoir besoin de plug-ins.), Comme dans mon expérience.

Le support des clients (ou des personnes de support en ligne) pour l'écriture de plug-ins est beaucoup plus difficile que l'architecture

Ian Ringrose
la source
5

D'après mon expérience, les deux meilleures façons de créer une architecture de plug-in flexible sont les langages de script et les bibliothèques. Ces deux concepts sont dans mon esprit orthogonaux; les deux peuvent être mélangés dans n'importe quelle proportion, un peu comme la programmation fonctionnelle et orientée objet, mais trouvent leurs plus grands atouts lorsqu'ils sont équilibrés. Une bibliothèque est généralement chargée de remplir une interface spécifique avec des fonctionnalités dynamiques, tandis que les scripts ont tendance à mettre l'accent sur les fonctionnalités avec une interface dynamique.

J'ai trouvé qu'une architecture basée sur des 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, et les bibliothèques sont ainsi libérées de toute interface spécifique, laissant toute l'interaction au niveau de l'application entre les mains plus flexibles du système de script.

Pour que cela fonctionne, le système de script doit avoir une API assez robuste, avec des crochets aux données d'application, à la logique et à l'interface graphique, ainsi que la fonctionnalité de base d'importation et d'exécution de code à partir de bibliothèques. En outre, les scripts doivent généralement être sûrs dans le sens où l'application peut récupérer gracieusement 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 ™.

Les moyens d'empaqueter les plugins dépendent 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, par exemple PluginName.extdans le répertoire racine.

Jon Purdy
la source
3

Je pense que vous devez d'abord répondre à la question: "Quels composants sont censés être des plugins?" Vous voulez garder ce nombre à un 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 plugin.

J'ai trouvé que le principal IOC (Inversion of Control) (read springframework) fonctionne bien pour fournir une base flexible, à laquelle vous pouvez ajouter une spécialisation pour simplifier le développement de plugins.

  • Vous pouvez scanner le conteneur à la recherche du mécanisme «interface en tant que publicité de type plugin».
  • Vous pouvez utiliser le conteneur pour injecter des dépendances communes dont les plugins peuvent avoir besoin (par exemple, ResourceLoaderAware ou MessageSourceAware).
Justin
la source
1

Le modèle de plug-in est un modèle logiciel pour étendre le comportement d'une classe avec une interface propre. Le comportement des classes est souvent étendu par l'héritage de classe, où la classe dérivée écrase certaines des méthodes virtuelles de la classe. Un problème avec cette solution est qu'elle entre en conflit avec le masquage de l'implémentation. Cela conduit également à des situations où la classe dérivée devient un lieu de rassemblement d'extensions de comportement indépendantes. De plus, le script est utilisé pour implémenter ce modèle comme mentionné ci-dessus "Faites des internes comme langage de script et écrivez tous les éléments de haut niveau dans ce langage. Cela le rend tout à fait modifiable et pratiquement à l'épreuve du futur". Les bibliothèques utilisent des bibliothèques de gestion de scripts. Le langage de script permet une manipulation de haut niveau des bibliothèques de niveau inférieur. (Aussi comme mentionné ci-dessus)

PresB4Me
la source