language agnostic - ¿Cómo crear una arquitectura flexible de plug-in?
language-agnostic architecture (8)
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 abordado 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 mezclada con datos
- Intentar esto con una jerarquía de herencia requiere conocimiento sobre los complementos a codificar, lo que significa que la arquitectura del complemento 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.
A medida que busco aprender de las diversas arquitecturas con las que he trabajado, también busco sugerencias de la comunidad. ¿Cómo se implementó una arquitectura de plug-in SOLID? ¿Cuál fue tu peor falla (o la peor falla que has visto)? ¿Qué harías si fuera a implementar una nueva arquitectura de complemento? ¿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:
- Módulo Perl :: Plugable e IOC para inyección de dependencia en Perl
- Los diversos frameworks Spring (Java, .NET, Python) para la inyección de dependencias.
- Una pregunta SO con una lista para Java (incluidas las interfaces del proveedor de servicios )
- Una pregunta SO para C ++ que apunta a un artículo del Dr. Dobbs
- Una pregunta de SO sobre una idea de complemento específico para ASP.NET MVC
Estos ejemplos parecen jugar a varios puntos fuertes del lenguaje. ¿Está una buena arquitectura de plugins necesariamente ligada al lenguaje? ¿Es mejor usar herramientas para crear una arquitectura de complemento o hacerlo en los siguientes modelos?
Una de las mejores arquitecturas de plugins 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 de plug-in.
http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html
Usualmente uso MEF. El Framework 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 ...
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, ¡que 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 va a admitir el 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 la línea Fount) que escriben Plug-Ins es mucho más difícil que la Arquitectura.
Creo que primero debes responder la pregunta: "¿Qué componentes se espera que sean complementos?" Desea mantener este número al mínimo absoluto o la cantidad 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 principal de IOC (Inversión del 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 ver 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).
En mi experiencia, las dos mejores formas de crear una arquitectura de complemento flexible son las bibliotecas y los lenguajes de scripting. Estos dos conceptos están en mi mente ortogonales; los dos se pueden mezclar en cualquier proporción, más bien como programación funcional y orientada a objetos, pero encuentran sus mayores fortalezas cuando están equilibrados. Una biblioteca es típicamente responsable de cumplir con una interfaz específica con funcionalidad dinámica, mientras que las secuencias de comandos tienden a enfatizar la funcionalidad con una interfaz dinámica.
Descubrí que una arquitectura basada en scripts que manejan bibliotecas parece funcionar mejor. El lenguaje de scripting permite la manipulación de alto nivel de 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 scripting.
Para que esto funcione, el sistema de scripting debe tener una API bastante robusta, con enlaces a los datos de la aplicación, la lógica y la GUI, así como la funcionalidad básica de importar y ejecutar código desde las bibliotecas. Además, los scripts generalmente son necesarios para que sean seguros en el sentido de que la aplicación puede recuperarse con gracia de un script mal escrito. Usar un sistema de guiones como una capa de indirección significa que la aplicación puede separarse más fácilmente en caso de Something Bad ™.
Los medios para empaquetar complementos dependen en gran medida de las preferencias personales, pero nunca se puede equivocar con un archivo comprimido con una interfaz simple, digamos PluginName.ext
en el directorio raíz.
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 se pierde la comodidad de poder usar la reflexión.
Una interfaz IPlugin
se usa para identificar clases que implementan complementos. Los métodos se agregan a la interfaz para permitir que la aplicación se comunique con el complemento. Por ejemplo, el método Init
que la aplicación usará para indicar al complemento que se inicie.
Para encontrar complementos, la aplicación escanea una carpeta de plugins para conjuntos .Net. Cada ensamblaje está cargado. La reflexión se usa para buscar clases que implementen IPlugin
. Se crea una instancia de cada clase de complemento.
(Alternativamente, un archivo Xml podría listar los ensamblados y las clases para cargar. Esto podría ayudar al rendimiento, pero nunca encontré un problema con el rendimiento).
Se llama al método Init
para cada objeto de complemento. Se pasa una referencia a un objeto que implementa la interfaz de la aplicación: IApplication
(o alguna otra cosa específica para su aplicación, por ejemplo, ITextEditorApplication).
IApplication
contiene 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á una propiedad OpenDocuments
que permite que los complementos enumeren la colección de documentos actualmente abiertos.
Este sistema de complemento se puede extender a los lenguajes de scripting, por ejemplo, Lua, mediante la creación de una clase de complemento derivado, por ejemplo, LuaPlugin
que envía IPlugin
funciones de IPlugin
y la interfaz de la aplicación a un script de Lua.
Esta técnica le permite implementar iterativamente su IPlugin
, IApplication
y otras interfaces específicas de la aplicación durante el desarrollo. Cuando la aplicación está completa y refactorizada, puede documentar las interfaces expuestas y debe tener un buen sistema para que los usuarios puedan escribir sus propios complementos.
En mi experiencia, he descubierto que en realidad hay dos tipos de arquitecturas enchufables.
- Uno sigue el
Eclipse model
que debe permitir la libertad y es abierto. - El otro generalmente requiere que los complementos sigan una
narrow API
porque el complemento cumplirá una función específica.
Para decirlo de otra manera, 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 distiction ... desea ambos para su aplicación.
No tengo mucha experiencia con Eclipse / Apertura de su aplicación para el modelo de complementos (el artículo en la publicación de Kalkie es genial). He leído un poco sobre la forma en que el 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 hecho ha utilizado una arquitectura de complemento para permitir que mi aplicación acceda a complementos, como datos de tiempo / visualización / mapa, etc.
Hace años, creaba fábricas, administradores de complementos y archivos de configuración para administrar todo y dejarme determinar qué complemento usar en tiempo de ejecución.
- Ahora solo suelo tener un
DI framework
hacer la mayor parte de ese trabajo.
Todavía tengo que escribir adaptadores para usar bibliotecas de terceros, pero generalmente no son tan malas.
Esta no es una respuesta tanto como un montón de observaciones / ejemplos potencialmente útiles.
Una forma efectiva de hacer que su aplicación sea extensible es exponer sus aspectos internos como un lenguaje de scripting 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 primitivos están bien elegidos e implementados). Una historia de éxito de este tipo de cosas es Emacs. Prefiero esto al sistema de complemento de eclipse style porque si quiero extender la funcionalidad, no tengo que aprender la API y escribir / compilar un plugin separado. Puedo escribir un fragmento de 3 líneas en el buffer 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 exclusivamente en los ganchos llamados en todos los niveles. Puede anular estos ganchos en archivos / funciones nombrados según una convención y alterar el comportamiento. Puede ver la lista de complementos en el sitio para ver qué tan rápido / fácil se pueden implementar.
Algunos puntos generales.
- Intenta mantener tu núcleo no extensible / no modificable por el usuario lo más pequeño posible. Delegue todo lo que pueda a una capa más alta para que la extensibilidad aumente. Menos cosas para corregir en el núcleo luego en caso de malas elecciones.
- En relación con el punto anterior, no debe tomar demasiadas decisiones sobre la dirección de su proyecto desde el principio. Implemente el subconjunto más pequeño que se necesita y luego comience a escribir complementos.
- Si está integrando un lenguaje de scripting, asegúrese de que sea uno completo en el que pueda escribir programas generales y no un lenguaje de juguete solo para su aplicación.
- Reduce el texto repetitivo tanto como puedas. No te preocupes por subclases, API complejas, registro de complementos y cosas así. Trate de mantenerlo simple para que sea fácil y no solo posible extenderlo. Esto permitirá que su API de complemento se use más y alentará a los usuarios finales a escribir complementos. No solo desarrolladores de complementos. py.test hace esto bien. Eclipse por lo que yo sé, no lo hace .