Wie erstelle ich eine flexible Plug-In-Architektur?

154

Ein sich wiederholendes Thema in meiner Entwicklungsarbeit war die Verwendung oder Erstellung einer internen Plug-In-Architektur. Ich habe gesehen, wie es auf viele Arten angegangen wurde - Konfigurationsdateien (XML, .conf usw.), Vererbungsframeworks, Datenbankinformationen, Bibliotheken und andere. Durch meine Erfahrung:

  • Eine Datenbank ist kein großartiger Ort, um Ihre Konfigurationsinformationen zu speichern, insbesondere wenn sie mit Daten vermischt sind
  • Um dies mit einer Vererbungshierarchie zu versuchen, sind Kenntnisse über die zu codierenden Plug-Ins erforderlich, was bedeutet, dass die Plug-In-Architektur nicht allzu dynamisch ist
  • Konfigurationsdateien eignen sich gut für die Bereitstellung einfacher Informationen, können jedoch nicht mit komplexeren Verhaltensweisen umgehen
  • Bibliotheken scheinen gut zu funktionieren, aber die Einwegabhängigkeiten müssen sorgfältig erstellt werden.

Da ich von den verschiedenen Architekturen lernen möchte, mit denen ich gearbeitet habe, suche ich auch bei der Community nach Vorschlägen. Wie haben Sie eine SOLID-Plug-In-Architektur implementiert? Was war Ihr schlimmster Fehler (oder der schlimmste Fehler, den Sie gesehen haben)? Was würden Sie tun, wenn Sie eine neue Plug-In-Architektur implementieren würden? Welches SDK oder Open Source-Projekt, mit dem Sie gearbeitet haben, ist das beste Beispiel für eine gute Architektur?

Einige Beispiele, die ich selbst gefunden habe:

Diese Beispiele scheinen mit verschiedenen Sprachstärken zu spielen. Ist eine gute Plugin-Architektur unbedingt an die Sprache gebunden? Ist es am besten, Tools zu verwenden, um eine Plugin-Architektur zu erstellen, oder dies auf eigenen folgenden Modellen zu tun?

justkt
quelle
Können Sie sagen, warum eine Plugin-Architektur ein allgemeines Thema war? Welche Probleme löst es oder welche Ziele werden angesprochen? Viele erweiterbare Systeme / Anwendungen verwenden Plugins in irgendeiner Form, aber die Form variiert erheblich je nach dem zu lösenden Problem.
Mdma
@mdma - das ist eine gute Frage. Ich würde sagen, dass ein erweiterbares System einige gemeinsame Ziele hat. Vielleicht sind die Ziele alles, was gemeinsam ist, und die beste Lösung hängt davon ab, wie erweiterbar das System sein muss, in welcher Sprache das System geschrieben ist und so weiter. Ich sehe jedoch, dass Muster wie IOC in vielen Sprachen angewendet werden, und ich wurde gebeten, immer wieder ähnliche Plugins (für Drop-in-Pieces, die auf dieselben Funktionsanforderungen reagieren) zu erstellen. Ich denke, es ist gut, sich einen Überblick über Best Practices für verschiedene Arten von Plugins zu verschaffen.
Justkt

Antworten:

89

Dies ist weniger eine Antwort als eine Reihe potenziell nützlicher Bemerkungen / Beispiele.

  • Eine effektive Möglichkeit, Ihre Anwendung erweiterbar zu machen, besteht darin, die Interna als Skriptsprache verfügbar zu machen und alle Inhalte der obersten Ebene in dieser Sprache zu schreiben. Dies macht es ziemlich modifizierbar und praktisch zukunftssicher (wenn Ihre Grundelemente gut ausgewählt und implementiert sind). Eine Erfolgsgeschichte dieser Art ist Emacs. Ich ziehe dies dem Plugin-System im Eclipse-Stil vor, da ich, wenn ich die Funktionalität erweitern möchte, die API nicht lernen und kein separates Plugin schreiben / kompilieren muss. Ich kann ein 3-Zeilen-Snippet in den aktuellen Puffer selbst schreiben, es auswerten und verwenden. Sehr reibungslose Lernkurve und sehr erfreuliche Ergebnisse.

  • Eine Anwendung, die ich ein wenig erweitert habe, ist Trac . Es verfügt über eine Komponentenarchitektur, die in dieser Situation bedeutet, dass Aufgaben an Module delegiert werden, die Erweiterungspunkte ankündigen. Sie können dann andere Komponenten implementieren, die in diese Punkte passen und den Fluss ändern. Es ist ein bisschen wie Kalkies Vorschlag oben.

  • Eine andere, die gut ist, ist py.test . Es folgt der Philosophie "Beste API ist keine API" und basiert ausschließlich auf Hooks, die auf jeder Ebene aufgerufen werden. Sie können diese Hooks in Dateien / Funktionen überschreiben, die gemäß einer Konvention benannt sind, und das Verhalten ändern. Sie können die Liste der Plugins auf der Site sehen, um zu sehen, wie schnell / einfach sie implementiert werden können.

Ein paar allgemeine Punkte.

  • Versuchen Sie, Ihren nicht erweiterbaren / nicht vom Benutzer veränderbaren Kern so klein wie möglich zu halten. Delegieren Sie alles, was Sie können, an eine höhere Ebene, damit die Erweiterbarkeit zunimmt. Im Kern weniger zu korrigieren als bei schlechten Entscheidungen.
  • Im Zusammenhang mit dem obigen Punkt sollten Sie zu Beginn nicht zu viele Entscheidungen über die Richtung Ihres Projekts treffen. Implementieren Sie die kleinste benötigte Teilmenge und beginnen Sie dann mit dem Schreiben von Plugins.
  • Wenn Sie eine Skriptsprache einbetten, stellen Sie sicher, dass diese vollständig ist und Sie allgemeine Programme und keine Spielzeugsprache nur für Ihre Anwendung schreiben können .
  • Reduzieren Sie die Boilerplate so weit wie möglich. Kümmern Sie sich nicht um Unterklassen, komplexe APIs, Plugin-Registrierung und ähnliches. Probieren Sie es einfach so zu halten , dass es einfach und nicht nur möglich zu verlängern. Dadurch kann Ihre Plugin-API häufiger verwendet werden und Endbenutzer werden dazu ermutigt, Plugins zu schreiben. Nicht nur Plugin-Entwickler. py.test macht das gut. Eclipse tut es meines Wissens nicht .
Noufal Ibrahim
quelle
1
Ich würde den Punkt der Skriptsprache dahingehend erweitern, dass es sich lohnt, eine vorhandene Sprache wie Python oder Perl einzubetten
Rudi
Wahre Rudi. Viele Sprachen werden hergestellt, um eingebettet zu werden. Guile zum Beispiel dient nur zum Einbetten. Dies spart Zeit, wenn Sie Ihre App skriptfähig machen. Sie können einfach in eine dieser Sprachen wechseln.
Noufal Ibrahim
24

Nach meiner Erfahrung gibt es zwei Arten von Plug-In-Architekturen.

  1. Man folgt dem, Eclipse modelwas Freiheit ermöglichen soll und offen ist.
  2. Das andere erfordert normalerweise, dass Plugins a folgen, narrow APIda das Plugin eine bestimmte Funktion erfüllt.

Um dies anders auszudrücken, ermöglicht eines Plugins den Zugriff auf Ihre Anwendung, während das andere Ihrer Anwendung den Zugriff auf Plugins ermöglicht .

Die Unterscheidung ist subtil und manchmal gibt es keine Unterscheidung ... Sie möchten beides für Ihre Anwendung.

Ich habe nicht viel Erfahrung mit Eclipse / Öffnen Ihrer App für Plugins-Modell (der Artikel in Kalkies Beitrag ist großartig). Ich habe ein bisschen darüber gelesen, wie Eclipse Dinge tut, aber nichts weiter.

In Yegges Eigenschaftenblog wird ein wenig darüber gesprochen, wie die Verwendung des Eigenschaftenmusters Plugins und Erweiterbarkeit ermöglicht.

Die meiste Arbeit, die ich geleistet habe, hat eine Plugin-Architektur verwendet, um meiner App den Zugriff auf Plugins zu ermöglichen, z. B. Zeit- / Anzeige- / Kartendaten usw.

Vor Jahren habe ich Fabriken, Plugin-Manager und Konfigurationsdateien erstellt, um alles zu verwalten, und mich bestimmen lassen, welches Plugin zur Laufzeit verwendet werden soll.

  • Jetzt muss ich normalerweise nur noch den DI frameworkgrößten Teil dieser Arbeit erledigen.

Ich muss noch Adapter schreiben, um Bibliotheken von Drittanbietern zu verwenden, aber sie sind normalerweise nicht so schlecht.

Menjaraz
quelle
@ Justin ja, DI = Abhängigkeitsinjektion.
Ableitung
14

Eine der besten Plug-In-Architekturen, die ich je gesehen habe, ist in Eclipse implementiert. Anstatt eine Anwendung mit einem Plug-In-Modell zu haben, ist alles ein Plug-In. Die Basisanwendung selbst ist das Plug-In-Framework.

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

Patrick
quelle
Sicher ein gutes Beispiel, aber ist es größer und komplexer als viele Anwendungen erfordern?
Justkt
Ja, es könnte sein, dass die Erhöhung der Flexibilität mit Kosten verbunden ist. Aber ich denke, es zeigt einen anderen Standpunkt. Warum konnte das Menü Ihrer Anwendung kein Plug-In oder die Hauptansicht sein?
Patrick
6
Der gleiche Ansatz (alles ist ein Plugin) wird in der neuen Version des Symfony-Frameworks verwendet. Sie werden Bündel genannt, aber das Prinzip ist dasselbe. Die Architektur ist ziemlich einfach, vielleicht sollten Sie sie sich ansehen: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld
@ Marjin Huizendveld - Sie sollten dies als separate Antwort hinzufügen, damit ich die Antwort positiv bewerten kann. Sieht nach einem interessanten Rahmen aus!
Justkt
11

Ich werde eine ziemlich einfache Technik beschreiben, die ich in der Vergangenheit verwendet habe. Dieser Ansatz verwendet C # -Reflexion, um den Ladevorgang des Plugins zu unterstützen. Diese Technik kann so geändert werden, dass sie auf C ++ anwendbar ist. Sie verlieren jedoch den Komfort, Reflektion verwenden zu können.

Eine IPluginSchnittstelle wird verwendet, um Klassen zu identifizieren, die Plugins implementieren. Der Schnittstelle werden Methoden hinzugefügt, damit die Anwendung mit dem Plugin kommunizieren kann. Zum Beispiel die InitMethode, mit der die Anwendung das Plugin zum Initialisieren anweist.

Um Plugins zu finden, durchsucht die Anwendung einen Plugin-Ordner nach .NET-Assemblys. Jede Baugruppe wird geladen. Reflection wird verwendet, um nach implementierten Klassen zu suchen IPlugin. Für jede Plugin-Klasse wird eine Instanz erstellt.

(Alternativ kann eine XML-Datei die zu ladenden Assemblys und Klassen auflisten. Dies kann die Leistung verbessern, aber ich habe nie ein Problem mit der Leistung gefunden.)

Die InitMethode wird für jedes Plugin-Objekt aufgerufen. Es wird ein Verweis auf ein Objekt übergeben, das die Anwendungsschnittstelle implementiert: IApplication(oder etwas anderes, das für Ihre App spezifisch ist, z. B. ITextEditorApplication).

IApplicationenthält Methoden, mit denen das Plugin mit der Anwendung kommunizieren kann. Wenn Sie beispielsweise einen Texteditor schreiben, verfügt diese Schnittstelle über eine OpenDocumentsEigenschaft, mit der Plugins die Sammlung aktuell geöffneter Dokumente auflisten können.

Dieses Plugin-System kann auf Skriptsprachen, z. B. Lua, erweitert werden, indem eine abgeleitete Plugin-Klasse erstellt wird, z. B. LuaPlugindie IPluginFunktionen und die Anwendungsschnittstelle an ein Lua-Skript weiterleitet .

Diese Technik ermöglicht es Ihnen , iterativ Ihre zu implementieren IPlugin, IApplicationwährend der Entwicklung und andere anwendungsspezifische Schnittstellen. Wenn die Anwendung vollständig und gut überarbeitet ist, können Sie Ihre exponierten Schnittstellen dokumentieren und Sie sollten ein schönes System haben, für das Benutzer ihre eigenen Plugins schreiben können.

Ashley Davis
quelle
7

Normalerweise benutze ich MEF. Das Managed Extensibility Framework (kurz MEF) vereinfacht die Erstellung erweiterbarer Anwendungen. MEF bietet Erkennungs- und Kompositionsfunktionen, mit denen Sie Anwendungserweiterungen laden können.

Wenn Sie interessiert sind, lesen Sie mehr ...

BALKANGraph
quelle
Danke, sieht interessant aus. Wie einfach ist es, ähnliche Prinzipien in anderen Sprachen anzuwenden?
Justkt
Eigentlich ist MEF nur für .NET Framework. Ich weiß nichts über andere Frameworks
BALKANGraph
7

Ich habe einmal an einem Projekt gearbeitet, das so flexibel sein musste, dass jeder Kunde das System einrichten konnte. Das einzig gute Design war, dem Kunden einen C # -Compiler zu liefern!

Wenn die Spezifikation mit Wörtern wie gefüllt ist:

  • Flexibel
  • Plug-In
  • Anpassbar

Stellen Sie viele Fragen dazu, wie Sie das System unterstützen (und wie der Support berechnet wird, da jeder Kunde der Meinung ist, dass sein Fall der Normalfall ist und keine Plug-Ins benötigt), wie ich es erlebt habe

Die Unterstützung von Kunden (oder Supportmitarbeitern), die Plug-Ins schreiben, ist viel schwieriger als die Architektur

Ian Ringrose
quelle
5

Nach meiner Erfahrung sind die beiden besten Möglichkeiten zum Erstellen einer flexiblen Plugin-Architektur Skriptsprachen und Bibliotheken. Diese beiden Konzepte sind meiner Meinung nach orthogonal; Die beiden können in jedem Verhältnis gemischt werden, ähnlich wie funktionale und objektorientierte Programmierung, finden aber ihre größten Stärken, wenn sie ausgewogen sind. Eine Bibliothek ist normalerweise dafür verantwortlich, eine bestimmte Schnittstelle mit dynamischer Funktionalität zu erfüllen , während Skripte die Funktionalität mit einer dynamischen Schnittstelle hervorheben .

Ich habe festgestellt, dass eine Architektur, die auf Skripten basiert, die Bibliotheken verwalten , am besten zu funktionieren scheint. Die Skriptsprache ermöglicht die Manipulation von Bibliotheken auf niedrigerer Ebene auf hoher Ebene, sodass die Bibliotheken von jeder spezifischen Schnittstelle befreit werden und die gesamte Interaktion auf Anwendungsebene in den flexibleren Händen des Skriptsystems verbleibt.

Damit dies funktioniert, muss das Skriptsystem über eine ziemlich robuste API verfügen, die Hooks zu Anwendungsdaten, Logik und GUI sowie die Basisfunktionalität zum Importieren und Ausführen von Code aus Bibliotheken enthält. Darüber hinaus müssen Skripte normalerweise in dem Sinne sicher sein, dass die Anwendung ordnungsgemäß von einem schlecht geschriebenen Skript wiederhergestellt werden kann. Durch die Verwendung eines Skriptsystems als Indirektionsebene kann sich die Anwendung im Falle von Something Bad ™ leichter lösen.

Die Art und Weise, wie Plugins verpackt werden, hängt weitgehend von Ihren persönlichen Vorlieben ab. Mit einem komprimierten Archiv mit einer einfachen Oberfläche, beispielsweise PluginName.extim Stammverzeichnis , können Sie jedoch nichts falsch machen .

Jon Purdy
quelle
3

Ich denke, Sie müssen zuerst die Frage beantworten: "Welche Komponenten werden voraussichtlich Plugins sein?" Sie möchten diese Anzahl auf ein absolutes Minimum beschränken oder die Anzahl der Kombinationen, die Sie testen müssen, explodiert. Versuchen Sie, Ihr Kernprodukt (das nicht zu flexibel sein sollte) von der Plugin-Funktionalität zu trennen.

Ich habe festgestellt, dass das IOC-Prinzip (Inversion of Control) (read springframework) gut funktioniert, um eine flexible Basis bereitzustellen, auf die Sie sich spezialisieren können, um die Plugin-Entwicklung zu vereinfachen.

  • Sie können den Container nach dem Mechanismus "Schnittstelle als Werbung vom Typ Plugin" durchsuchen.
  • Sie können den Container verwenden, um allgemeine Abhängigkeiten einzufügen, die Plugins möglicherweise benötigen (z. B. ResourceLoaderAware oder MessageSourceAware).
Justin
quelle
1

Das Plug-in-Muster ist ein Softwaremuster zum Erweitern des Verhaltens einer Klasse mit einer sauberen Schnittstelle. Oft wird das Verhalten von Klassen durch Klassenvererbung erweitert, wobei die abgeleitete Klasse einige der virtuellen Methoden der Klasse überschreibt. Ein Problem bei dieser Lösung besteht darin, dass sie mit dem Ausblenden der Implementierung in Konflikt steht. Es führt auch zu Situationen, in denen abgeleitete Klassen zu Treffpunkten nicht verwandter Verhaltenserweiterungen werden. Außerdem wird Scripting verwendet, um dieses Muster wie oben erwähnt zu implementieren. "Machen Sie Interna als Skriptsprache und schreiben Sie alle Inhalte der obersten Ebene in dieser Sprache. Dies macht es ziemlich modifizierbar und praktisch zukunftssicher." Bibliotheken verwenden Skriptverwaltungsbibliotheken. Die Skriptsprache ermöglicht die Manipulation von Bibliotheken auf niedrigerer Ebene auf hoher Ebene. (Auch wie oben erwähnt)

PresB4Me
quelle