¿Cómo crear una arquitectura de complemento flexible?

154

Un tema recurrente en mi trabajo de desarrollo ha sido el uso o la creación de una arquitectura de plug-in interna. Lo he visto acercarse de muchas maneras: archivos de configuración (XML, .conf, etc.), marcos de herencia, información de bases de datos, bibliotecas y otros. En mi experiencia:

  • Una base de datos no es un gran lugar para almacenar su información de configuración, especialmente combinada con datos
  • Intentar esto con una jerarquía de herencia requiere conocimiento sobre los complementos a codificar, lo que significa que la arquitectura de complementos no es tan dinámica
  • Los archivos de configuración funcionan bien para proporcionar información simple, pero no pueden manejar comportamientos más complejos
  • Las bibliotecas parecen funcionar bien, pero las dependencias unidireccionales deben crearse cuidadosamente.

Mientras busco aprender de las diversas arquitecturas con las que he trabajado, también estoy buscando sugerencias en la comunidad. ¿Cómo ha implementado una arquitectura de complemento SOLID? ¿Cuál fue tu peor fracaso (o el peor fracaso que has visto)? ¿Qué haría si implementara una nueva arquitectura de plug-in? ¿Qué SDK o proyecto de código abierto con el que has trabajado tiene el mejor ejemplo de una buena arquitectura?

Algunos ejemplos que he encontrado por mi cuenta:

Estos ejemplos parecen jugar con varias fortalezas del lenguaje. ¿Una buena arquitectura de complementos está necesariamente vinculada al lenguaje? ¿Es mejor usar herramientas para crear una arquitectura de plugin, o hacerlo en los siguientes modelos?

justkt
fuente
¿Puedes decir por qué una arquitectura de complementos ha sido un tema común? ¿Qué problemas resuelve u objetivos aborda? Muchos sistemas / aplicaciones extensibles usan complementos de alguna forma, pero la forma varía considerablemente según el problema que se resuelva.
mdma
@mdma: esa es una gran pregunta. Yo diría que hay algunos objetivos comunes en un sistema extensible. Quizás los objetivos son todo lo común, y la mejor solución varía según cuán extensible deba ser el sistema, en qué idioma está escrito el sistema, etc. Sin embargo, veo que patrones como IOC se aplican en muchos idiomas, y me han pedido que haga complementos similares (para piezas que responden a las mismas solicitudes de funcionalidad) una y otra vez. Creo que es bueno tener una idea general de las mejores prácticas para varios tipos de complementos.
justkt

Respuestas:

89

Esta no es una respuesta tanto como un montón de comentarios / ejemplos potencialmente útiles.

  • Una forma efectiva de hacer que su aplicación sea extensible es exponer sus elementos internos como un lenguaje de script y escribir todas las cosas de nivel superior en ese idioma. Esto lo hace bastante modificable y prácticamente a prueba de futuro (si sus primitivas están bien elegidas e implementadas). Una historia de éxito de este tipo de cosas es Emacs. Prefiero esto al sistema de complementos de estilo eclipse porque si quiero ampliar la funcionalidad, no tengo que aprender la API y escribir / compilar un complemento por separado. Puedo escribir un fragmento de 3 líneas en el búfer actual, evaluarlo y usarlo. Curva de aprendizaje muy suave y resultados muy agradables.

  • Una aplicación que he extendido un poco es Trac . Tiene una arquitectura de componentes que, en esta situación, significa que las tareas se delegan en módulos que anuncian puntos de extensión. Luego puede implementar otros componentes que encajarían en estos puntos y cambiar el flujo. Es un poco como la sugerencia de Kalkie anterior.

  • Otro que es bueno es py.test . Sigue la filosofía de "la mejor API no es API" y se basa únicamente en ganchos que se llaman en todos los niveles. Puede anular estos enlaces en archivos / funciones nombrados de acuerdo con una convención y alterar el comportamiento. Puede ver la lista de complementos en el sitio para ver qué tan rápido / fácil pueden implementarse.

Algunos puntos generales.

  • Intente mantener su núcleo no extensible / no modificable por el usuario lo más pequeño posible. Delegue todo lo que pueda a una capa superior para que aumente la extensibilidad. Menos cosas para corregir en el núcleo que en caso de malas elecciones.
  • Relacionado con el punto anterior es que no debe tomar demasiadas decisiones sobre la dirección de su proyecto desde el principio. Implemente el subconjunto más pequeño necesario y luego comience a escribir complementos.
  • Si está incorporando un lenguaje de secuencias de comandos, asegúrese de que sea completo en el que pueda escribir programas generales y no un lenguaje de juguete solo para su aplicación.
  • Reduzca el repetitivo tanto como pueda. No te molestes con subclases, API complejas, registro de complementos y cosas por el estilo. Trate de mantenerlo simple para que sea fácil y no solo posible extenderlo. Esto permitirá que su API de complementos se use más y alentará a los usuarios finales a escribir complementos. No solo desarrolladores de complementos. py.test hace esto bien. Eclipse, que yo sepa, no lo hace .
Noufal Ibrahim
fuente
1
Extendería el punto del lenguaje de secuencias de comandos a que vale la pena considerar incorporar un lenguaje existente como python o perl
Rudi
Verdadero Rudi. Muchos idiomas están siendo diseñados para ser integrados. Guile, por ejemplo, está hecho solo para incrustar. Esto ahorra tiempo si está haciendo que su aplicación sea programable. Puedes dejar caer en uno de estos idiomas.
Noufal Ibrahim
24

En mi experiencia, he encontrado que realmente hay dos tipos de arquitecturas de complemento.

  1. Uno sigue lo Eclipse modelque está destinado a permitir la libertad y es abierto.
  2. El otro generalmente requiere complementos para seguir a narrow APIporque el complemento completará una función específica.

Para decir esto de una manera diferente, uno permite que los complementos accedan a su aplicación, mientras que el otro permite que su aplicación acceda a los complementos .

La distinción es sutil y, a veces, no hay discreción ... desea ambos para su aplicación.

No tengo mucha experiencia con Eclipse / Abrir tu aplicación al modelo de complementos (el artículo en la publicación de Kalkie es genial). He leído un poco sobre la forma en que eclipse hace las cosas, pero nada más que eso.

El blog de propiedades de Yegge habla un poco sobre cómo el uso del patrón de propiedades permite complementos y extensibilidad.

La mayor parte del trabajo que he realizado ha utilizado una arquitectura de complementos para permitir que mi aplicación acceda a complementos, cosas como tiempo / visualización / datos de mapas, etc.

Hace años, creé fábricas, administradores de complementos y archivos de configuración para administrar todo y me permitieron determinar qué complemento usar en tiempo de ejecución.

  • Ahora por lo general solo tengo que DI frameworkhacer la mayor parte de ese trabajo.

Todavía tengo que escribir adaptadores para usar bibliotecas de terceros, pero generalmente no son tan malos.

menjaraz
fuente
@Justin sí, DI = inyección de dependencia.
derivación
14

Una de las mejores arquitecturas de complementos que he visto se implementa en Eclipse. En lugar de tener una aplicación con un modelo de complemento, todo es un complemento. La aplicación base en sí es el marco del complemento.

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

Patricio
fuente
Ciertamente es un buen ejemplo, pero ¿es más grande y más complejo de lo que requieren muchas aplicaciones?
justkt
Sí, podría ser, el aumento de la flexibilidad tiene un costo. Pero creo que muestra un punto de vista diferente. ¿Por qué el menú de su aplicación no puede ser un complemento o la vista principal?
Patrick
66
El mismo enfoque (todo es un complemento) se utiliza en la nueva versión del marco de Symfony. Se llaman paquetes pero el principio es el mismo. Su arquitectura es bastante simple, tal vez deberías echarle un vistazo: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld
@Marjin Huizendveld: debe agregar esto como una respuesta separada para que pueda votar la respuesta. ¡Parece un marco interesante!
justkt
11

Describiré una técnica bastante simple que he usado en el pasado. Este enfoque utiliza la reflexión de C # para ayudar en el proceso de carga del complemento. Esta técnica se puede modificar para que sea aplicable a C ++, pero pierde la conveniencia de poder usar la reflexión.

Se IPluginutiliza una interfaz para identificar clases que implementan complementos. Se agregan métodos a la interfaz para permitir que la aplicación se comunique con el complemento. Por ejemplo, el Initmétodo que utilizará la aplicación para indicar al complemento que se inicialice.

Para buscar complementos, la aplicación escanea una carpeta de complementos en busca de ensamblados .Net. Cada conjunto está cargado. La reflexión se utiliza para buscar clases que implementan IPlugin. Se crea una instancia de cada clase de complemento.

(Alternativamente, un archivo Xml puede enumerar los ensamblados y las clases para cargar. Esto podría ayudar al rendimiento, pero nunca encontré un problema con el rendimiento).

Se Initllama al método para cada objeto de complemento. Se le pasa una referencia a un objeto que implementa la interfaz de la aplicación: IApplication(o algo más llamado específico para su aplicación, por ejemplo, ITextEditorApplication).

IApplicationcontiene métodos que permiten que el complemento se comunique con la aplicación. Por ejemplo, si está escribiendo un editor de texto, esta interfaz tendría una OpenDocumentspropiedad que permite que los complementos enumeren la colección de documentos abiertos actualmente.

Este sistema de complemento se puede extender a los lenguajes de secuencias de comandos, por ejemplo, Lua, creando una clase de complemento derivada, por ejemplo, LuaPluginque reenvía las IPluginfunciones y la interfaz de la aplicación a un script de Lua.

Esta técnica le permite implementar de forma iterativa sus IPlugin, IApplicationy otras interfaces específicas de la aplicación durante el desarrollo. Cuando la aplicación está completa y bien refactorizada, puede documentar sus interfaces expuestas y debe tener un buen sistema para que los usuarios puedan escribir sus propios complementos.

Ashley Davis
fuente
7

Usualmente uso MEF. El Marco de Extensibilidad Administrada (o MEF para abreviar) simplifica la creación de aplicaciones extensibles. MEF ofrece capacidades de descubrimiento y composición que puede aprovechar para cargar extensiones de aplicaciones.

Si estás interesado lee más ...

BALKANGraph
fuente
Gracias, se ve interesante. ¿Qué tan fácil es aplicar principios similares en otros idiomas?
justkt
En realidad, MEF es solo para .NET Framework. No sé nada sobre otros frameworks
BALKANGraph
7

Una vez trabajé en un proyecto que tenía que ser tan flexible en la forma en que cada cliente podía configurar el sistema, ¡el único buen diseño que encontramos fue enviar al cliente un compilador de C #!

Si la especificación está llena de palabras como:

  • Flexible
  • Enchufar
  • Personalizable

Haga muchas preguntas sobre cómo brindará soporte al sistema (y cómo se cobrará el soporte, ya que cada cliente considerará que su caso es el caso normal y no debería necesitar ningún complemento), como en mi experiencia

El soporte de clientes (o personas de soporte de línea de fuente) que escriben complementos es mucho más difícil que la arquitectura

Ian Ringrose
fuente
5

En mi experiencia, las dos mejores formas de crear una arquitectura de plugin flexible son los lenguajes de scripting y las bibliotecas. Estos dos conceptos son, en mi opinión, ortogonales; los dos se pueden mezclar en cualquier proporción, más bien como la programación funcional y orientada a objetos, pero encuentran sus mayores fortalezas cuando se equilibran. Una biblioteca es típicamente responsable de cumplir una interfaz específica con funcionalidad dinámica, mientras que los scripts tienden a enfatizar la funcionalidad con una interfaz dinámica.

He encontrado que una arquitectura basada en scripts que manejan bibliotecas parece funcionar mejor. El lenguaje de secuencias de comandos permite la manipulación de alto nivel de las bibliotecas de nivel inferior, y las bibliotecas se liberan de cualquier interfaz específica, dejando toda la interacción a nivel de aplicación en las manos más flexibles del sistema de secuencias de comandos.

Para que esto funcione, el sistema de secuencias de comandos debe tener una API bastante robusta, con enlaces a los datos, la lógica y la GUI de la aplicación, así como la funcionalidad básica de importar y ejecutar código desde bibliotecas. Además, generalmente se requiere que los scripts sean seguros en el sentido de que la aplicación puede recuperarse con gracia de un script mal escrito. El uso de un sistema de secuencias de comandos como una capa de indirección significa que la aplicación puede separarse más fácilmente en caso de Something Bad ™.

El medio de empaquetar complementos depende en gran medida de las preferencias personales, pero nunca puede equivocarse con un archivo comprimido con una interfaz simple, por ejemplo, PluginName.exten el directorio raíz.

Jon Purdy
fuente
3

Creo que primero debe responder la pregunta: "¿Qué componentes se espera que sean complementos?" Desea mantener este número al mínimo absoluto o el número de combinaciones que debe probar explota. Intente separar su producto principal (que no debería tener demasiada flexibilidad) de la funcionalidad del complemento.

Descubrí que el director de IOC (Inversión de control) (leer springframework) funciona bien para proporcionar una base flexible, a la que puede agregar especialización para simplificar el desarrollo de complementos.

  • Puede escanear el contenedor para el mecanismo de "interfaz como un anuncio de tipo de complemento".
  • Puede usar el contenedor para inyectar dependencias comunes que los complementos pueden requerir (es decir, ResourceLoaderAware o MessageSourceAware).
Justin
fuente
1

El patrón de complemento es un patrón de software para extender el comportamiento de una clase con una interfaz limpia. A menudo, el comportamiento de las clases se extiende por la herencia de clases, donde la clase derivada sobrescribe algunos de los métodos virtuales de la clase. Un problema con esta solución es que entra en conflicto con el ocultamiento de la implementación. También conduce a situaciones donde la clase derivada se convierte en un lugar de reunión de extensiones de comportamiento no relacionadas. Además, las secuencias de comandos se utilizan para implementar este patrón como se mencionó anteriormente "Hacer elementos internos como un lenguaje de secuencias de comandos y escribir todas las cosas de nivel superior en ese idioma. Esto lo hace bastante modificable y prácticamente a prueba de futuro". Las bibliotecas utilizan bibliotecas de gestión de scripts. El lenguaje de secuencias de comandos permite la manipulación de alto nivel de las bibliotecas de nivel inferior. (También como se mencionó anteriormente)

PresB4Me
fuente