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

Un thème récurrent dans mon travail de développement a été l’utilisation ou la création d’une architecture de plug-in interne. Je l’ai vu approcher de nombreuses façons: fichiers de configuration (XML, .conf, etc.), structures d’inheritance, informations de firebase database, bibliothèques et autres. Dans mon expérience:

  • Une firebase database n’est pas un endroit idéal pour stocker vos informations de configuration, en particulier celles associées aux données.
  • Tenter ceci avec une hiérarchie d’inheritance nécessite de connaître les plug-ins à coder, ce qui signifie que l’architecture du plug-in n’est pas si dynamic
  • 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 créées avec soin.

Comme je cherche à apprendre des différentes architectures avec lesquelles j’ai travaillé, je me tourne également vers la communauté pour obtenir 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 avez vu)? Que feriez-vous si vous envisagiez de mettre en place une nouvelle architecture de plug-in? 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 tout seul:

  • Module Perl :: Plugable et IOC pour l’dependency injections en Perl
  • Les différents frameworks Spring (Java, .NET, Python) pour l’dependency injections.
  • Une question SO avec une liste pour Java (y compris les interfaces de fournisseur de services )
  • Une question SO pour C ++ pointant vers un article de Dr. Dobbs
  • Une question SO concernant une idée de plugin spécifique pour ASP.NET MVC

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 les modèles suivants?

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 cette langue. Cela en fait une preuve assez modifiable et pratiquement future (si vos primitives sont bien choisies et implémentées). Emacs est une réussite. 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 et à écrire / comstackr 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 résultats très agréables.

  • Une application que j’ai étendue un peu est Trac . Il possède 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 stream. C’est un peu comme la suggestion de Kalkie ci-dessus.

  • Un autre qui est bon est py.test . Il suit la philosophie de la “meilleure API n’est pas une API” et repose uniquement sur des hooks appelés à 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 implémentés.

Quelques points généraux

  • Essayez de garder votre kernel non extensible / non modifiable par l’utilisateur aussi petit que possible. Déléguez tout ce que vous pouvez sur un calque supérieur afin que l’extensibilité augmente. Moins de choses à corriger dans le kernel puis en cas de mauvais choix.
  • En liaison avec le point ci-dessus, vous ne devriez pas prendre trop de décisions sur l’orientation de votre projet au départ. Implémentez le plus petit sous-ensemble requirejs, puis commencez à écrire des plug-ins.
  • 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 pas un langage pour vos applications.
  • Réduisez autant que possible le passe-partout. Ne vous souciez pas des sous-classes, des API complexes, de l’enregistrement des plug-ins, etc. Essayez de garder les choses simples pour que ce soit facile et pas seulement possible . Cela permettra à votre API de plug-in d’être utilisée davantage et encouragera les utilisateurs finaux à écrire des plug-ins. Pas seulement les développeurs de plug-ins. py.test le fait bien. Eclipse pour autant que je sache, pas .

D’après mon expérience, il existe deux types d’architecture 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 des plug-ins pour suivre une narrow API car le plug-in remplira une fonction spécifique.

Pour énoncer ceci différemment, on permet aux plugins d’accéder à votre application tandis que l’autre permet à votre application d’accéder aux plugins .

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

Je n’ai pas beaucoup d’expérience avec Eclipse / Ouvrir votre application au modèle de plugins (l’article dans l’article de Kalkie est génial). J’ai lu un peu sur la façon dont éclipse fait les choses, mais rien de plus que cela.

Le blog de propriétés de Yegge parle un peu de la façon dont l’utilisation du modèle de propriétés permet les plugins et l’extensibilité.

La plupart du travail que j’ai effectué a utilisé une architecture de plug-in pour permettre à mon application d’accéder à des plugins, des choses comme les 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 laisser déterminer quel plug-in utiliser à l’exécution.

  • Maintenant, j’ai généralement juste un DI framework faire 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.

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 la structure de plug-in.

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

Une fois, j’ai travaillé sur un projet qui devait être si flexible que chaque client pouvait configurer le système, dont le seul bon design était de fournir au client un compilateur C #!

Si la spécification est remplie de mots comme:

  • Flexible
  • Brancher
  • Personnalisable

Posez beaucoup de questions sur la façon dont vous allez supporter le système (et comment le support sera facturé, chaque client pensant 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 de ligne) écrivant des plug-ins est beaucoup plus difficile que l’architecture

Habituellement, j’utilise MEF. La structure d’extensibilité gérée (MEF) simplifie la création d’applications extensibles. MEF offre des fonctionnalités de découverte et de composition que vous pouvez exploiter pour charger des extensions d’applications.

Si vous êtes intéressé, lisez la suite …

Je décrirai une technique assez simple que j’ai utilisée par le passé. Cette approche utilise la reflection C # pour faciliter le processus de chargement des plug-ins. Cette technique peut être modifiée afin qu’elle soit applicable en C ++, mais vous perdez la possibilité de pouvoir utiliser la reflection.

Une interface IPlugin est utilisée pour identifier les classes implémentant des plug-ins. Des méthodes sont ajoutées à l’interface pour permettre à l’application de communiquer avec le plug-in. Par exemple, la méthode Init utilisée par l’application pour demander au plug-in de s’initialiser.

Pour rechercher des plug-ins, l’application parsing un dossier de plug-in pour les assemblys .Net. Chaque assemblage est chargé. Reflection est utilisé pour rechercher les classes implémentant IPlugin . Une instance de chaque classe de plug-in est créée.

(Sinon, un fichier XML peut répertorier les assemblys et les classes à charger. Cela peut aider les performances, mais je n’ai jamais rencontré de problème de performances).

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

IApplication contient des méthodes permettant au plug-in de communiquer avec l’application. Par exemple, si vous écrivez un éditeur de texte, cette interface aura une propriété OpenDocuments qui permettra aux plug-ins d’énumérer la collection des 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 LuaPlugin qui transfère les fonctions IPlugin et l’interface de l’application vers 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 pendant le développement. Lorsque l’application est terminée et bien refait, vous pouvez documenter vos interfaces exposées et vous devriez avoir un bon système pour lequel les utilisateurs peuvent écrire leurs propres plugins.

Je pense que vous devez d’abord répondre à la question: “Quels composants devraient ê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é de plug-in.

J’ai trouvé que le principal IOC (Inversion of Control) (read springframework) fonctionne bien pour fournir une base flexible, à laquelle vous pouvez append une spécialisation pour rendre le développement de plugin plus simple.

  • Vous pouvez parsingr le conteneur pour le mécanisme “interface en tant que publication de type plug-in”.
  • Vous pouvez utiliser le conteneur pour injecter des dépendances communes dont les plugins peuvent avoir besoin (par exemple, ResourceLoaderAware ou MessageSourceAware).

Dans mon expérience, les deux meilleurs moyens 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, plutôt comme une functional programming et orientée object, mais trouver leurs plus grandes forces lorsqu’ils sont équilibrés. Une bibliothèque est généralement responsable de remplir une interface spécifique avec une fonctionnalité dynamic, tandis que les scripts ont tendance à mettre l’accent sur les fonctionnalités avec une interface dynamic.

J’ai trouvé qu’une architecture basée sur des scripts gérant des bibliothèques semble fonctionner de manière optimale. 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 dans les mains les plus flexibles du système de script.

Pour que cela fonctionne, le système de script doit disposer d’une API assez robuste, avec des points d’ancrage aux données d’application, à la logique et à l’interface graphique, ainsi que la fonctionnalité de base d’importation et d’exécution du code des bibliothèques. De plus, les scripts doivent généralement être sécurisés dans le sens où l’application peut récupérer avec grâce d’un script mal écrit. Utiliser 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 mode de conditionnement des plug-ins 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, par exemple PluginName.ext dans le répertoire racine.