Como criar uma arquitetura de plug-in flexível?

154

Um tema recorrente em meu trabalho de desenvolvimento foi o uso ou a criação de uma arquitetura de plug-in interna. Eu já vi várias abordagens - arquivos de configuração (XML, .conf e assim por diante), estruturas de herança, informações de banco de dados, bibliotecas e outros. Em minha experiência:

  • Um banco de dados não é um ótimo lugar para armazenar suas informações de configuração, especialmente combinadas com dados
  • Tentar isso com uma hierarquia de herança exige que o conhecimento sobre os plug-ins seja codificado, o que significa que a arquitetura do plug-in não é tão dinâmica
  • Os arquivos de configuração funcionam bem para fornecer informações simples, mas não podem lidar com comportamentos mais complexos
  • As bibliotecas parecem funcionar bem, mas as dependências unidirecionais precisam ser criadas com cuidado.

Como procuro aprender com as várias arquiteturas com as quais trabalhei, também busco sugestões na comunidade. Como você implementou uma arquitetura de plug-in SOLID? Qual foi o seu pior fracasso (ou o pior fracasso que você já viu)? O que você faria se implementasse uma nova arquitetura de plug-in? Qual projeto de código aberto ou SDK com o qual você trabalhou tem o melhor exemplo de uma boa arquitetura?

Alguns exemplos que encontrei por conta própria:

Esses exemplos parecem ter vários pontos fortes da linguagem. Uma boa arquitetura de plugins está necessariamente ligada ao idioma? É melhor usar ferramentas para criar uma arquitetura de plug-in ou fazê-lo por conta própria nos seguintes modelos?

justkt
fonte
Você pode dizer por que uma arquitetura de plug-in tem sido um tema comum? Que problemas ele resolve ou quais objetivos ele aborda? Muitos sistemas / aplicativos extensíveis usam plug-ins de alguma forma, mas a forma varia consideravelmente, dependendo do problema que está sendo resolvido.
Mdma
@ MDMA - que é uma ótima pergunta. Eu diria que existem alguns objetivos comuns em um sistema extensível. Talvez os objetivos sejam comuns e a melhor solução varie dependendo da extensão do sistema, do idioma em que o sistema está escrito e assim por diante. No entanto, vejo padrões como o COI sendo aplicados em vários idiomas e me pediram para fazer plug-ins semelhantes (para obter pedaços que respondem às mesmas solicitações de funcionalidade) repetidamente. Eu acho que é bom ter uma idéia geral das melhores práticas para vários tipos de plugins.
Just 13/05

Respostas:

89

Esta não é uma resposta , mas sim um monte de observações / exemplos potencialmente úteis.

  • Uma maneira eficaz de tornar seu aplicativo extensível é expor seus internos como uma linguagem de script e escrever todas as coisas de nível superior nessa linguagem. Isso o torna bastante modificável e praticamente à prova do futuro (se suas primitivas forem bem escolhidas e implementadas). Uma história de sucesso desse tipo de coisa é o Emacs. Eu prefiro isso ao sistema de plugins do estilo eclipse, porque se eu quiser estender a funcionalidade, não preciso aprender a API e escrever / compilar um plug-in separado. Eu posso escrever um trecho de 3 linhas no próprio buffer atual, avaliá-lo e usá-lo. Curva de aprendizado muito suave e resultados muito agradáveis.

  • Um aplicativo que estendi um pouco é o Trac . Possui uma arquitetura de componentes que, nessa situação, significa que as tarefas são delegadas a módulos que anunciam pontos de extensão. Você pode implementar outros componentes que se encaixariam nesses pontos e alterar o fluxo. É um pouco como a sugestão de Kalkie acima.

  • Outro bom é py.test . Segue a filosofia "melhor API não existe API" e depende exclusivamente dos ganchos que são chamados em todos os níveis. Você pode substituir esses ganchos em arquivos / funções nomeados de acordo com uma convenção e alterar o comportamento. Você pode ver a lista de plugins no site para ver com que rapidez / facilidade eles podem ser implementados.

Alguns pontos gerais.

  • Tente manter o seu núcleo não extensível / não modificável pelo usuário o mais pequeno possível. Delegue tudo o que puder para uma camada superior para aumentar a extensibilidade. Menos coisas para corrigir no núcleo do que em caso de más escolhas.
  • Relacionado ao ponto acima, você não deve tomar muitas decisões sobre a direção do seu projeto desde o início. Implemente o menor subconjunto necessário e comece a escrever plugins.
  • Se você estiver incorporando uma linguagem de script, verifique se é possível escrever programas gerais e não uma linguagem de brinquedo apenas para o seu aplicativo.
  • Reduza o máximo possível do clichê. Não se preocupe com subclasses, APIs complexas, registro de plug-ins e coisas assim. Tente mantê-lo simples, para que seja fácil e não apenas possível estender. Isso permitirá que sua API de plug-in seja mais usada e incentivará os usuários finais a escrever plug-ins. Não apenas desenvolvedores de plugins. py.test faz isso bem. Eclipse, tanto quanto eu sei, não .
Noufal Ibrahim
fonte
1
Gostaria de estender o ponto de linguagem de script para que vale a pena considerar para incorporar uma linguagem existente, como python ou perl
Rudi
Rudi verdadeiro. Muitos idiomas estão sendo criados para serem incorporados. Guile, por exemplo, é feito apenas para incorporação. Isso economiza tempo se você estiver transformando seu aplicativo em script. Você pode simplesmente soltar em um desses idiomas.
Noufal Ibrahim
24

Na minha experiência, descobri que existem realmente dois tipos de arquiteturas de plug-in.

  1. Segue-se o Eclipse modelque se destina a permitir a liberdade e é aberto.
  2. O outro normalmente requer que os plug-ins sigam um narrow APIporque o plug-in preencherá uma função específica.

Para declarar isso de uma maneira diferente, um permite que plug-ins acessem seu aplicativo, enquanto o outro permite que seu aplicativo acesse plug-ins .

A distinção é sutil e, às vezes, não há distinção ... você quer os dois para a sua aplicação.

Não tenho muita experiência com o Eclipse / Abrindo seu aplicativo para o modelo de plug-ins (o artigo na publicação de Kalkie é ótimo). Eu li um pouco sobre o modo como o eclipse faz as coisas, mas nada além disso.

Blog de propriedades de Yegge fala um pouco sobre como o uso do padrão de propriedades permite plugins e extensibilidade.

A maior parte do trabalho que fiz utilizou uma arquitetura de plug-in para permitir que meu aplicativo acesse plug-ins, coisas como hora / exibição / dados de mapa etc.

Anos atrás, eu criava fábricas, gerenciadores de plugins e arquivos de configuração para gerenciar tudo isso e deixava-me determinar qual plugin usar em tempo de execução.

  • Agora, normalmente, DI frameworkfaço a maior parte desse trabalho.

Ainda preciso escrever adaptadores para usar bibliotecas de terceiros, mas eles geralmente não são tão ruins assim.

menjaraz
fonte
@ Justin sim, DI = injeção de dependência.
derivação
14

Uma das melhores arquiteturas de plug-in que eu já vi é implementada no Eclipse. Em vez de ter um aplicativo com um modelo de plug-in, tudo é um plug-in. O aplicativo base em si é a estrutura de plug-in.

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

Patrick
fonte
Certamente um bom exemplo, mas é maior e mais complexo do que muitos aplicativos exigem?
justkt
Sim, o aumento da flexibilidade tem um custo. Mas acho que mostra um ponto de vista diferente. Por que o menu do seu aplicativo não pode ser um plug-in ou a visualização principal?
Patrick
6
A mesma abordagem (tudo é um plugin) é usada na nova versão do framework Symfony. Eles são chamados de pacotes, mas o princípio é o mesmo. Sua arquitetura é bastante simples, talvez você deve ter um olhar para ele: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld
@Marjin Huizendveld - você deve adicionar isso como uma resposta separada para que eu possa aprovar a resposta. Parece uma estrutura interessante!
Just #
11

Descreverei uma técnica bastante simples que utilizei no passado. Essa abordagem usa a reflexão em C # para ajudar no processo de carregamento do plug-in. Essa técnica pode ser modificada para que seja aplicável ao C ++, mas você perde a conveniência de poder usar a reflexão.

Uma IPlugininterface é usada para identificar classes que implementam plugins. Métodos são adicionados à interface para permitir que o aplicativo se comunique com o plug-in. Por exemplo, o Initmétodo que o aplicativo usará para instruir o plugin a inicializar.

Para encontrar plug-ins, o aplicativo verifica uma pasta de plug-ins para assemblies .Net. Cada montagem é carregada. O Reflection é usado para procurar classes que implementamIPlugin . Uma instância de cada classe de plug-in é criada.

(Como alternativa, um arquivo Xml pode listar os assemblies e classes a serem carregados. Isso pode ajudar no desempenho, mas nunca encontrei um problema com o desempenho).

O Initmétodo é chamado para cada objeto de plug-in. É passada uma referência a um objeto que implementa a interface do aplicativo:IApplication (ou algo mais específico do seu aplicativo, por exemplo, ITextEditorApplication).

IApplicationcontém métodos que permitem que o plug-in se comunique com o aplicativo. Por exemplo, se você estiver escrevendo um editor de texto, essa interface terá umOpenDocuments propriedade que permita aos plugins enumerar a coleção de documentos abertos no momento.

Este sistema de plugins pode ser estendido para linguagens de script, por exemplo, Lua, criando uma classe de plugins derivada, por exemplo, LuaPluginque encaminhaIPlugin funções e a interface do aplicativo para um script Lua.

Esta técnica permite-lhe implementar de forma iterativa seus IPlugin, IApplicatione outras interfaces específicas do aplicativo durante o desenvolvimento. Quando o aplicativo estiver completo e bem refatorado, é possível documentar suas interfaces expostas e você deve ter um bom sistema para o qual os usuários possam escrever seus próprios plugins.

Ashley Davis
fonte
7

Geralmente eu uso o MEF. O Managed Extensibility Framework (ou MEF) simplifica a criação de aplicativos extensíveis. O MEF oferece recursos de descoberta e composição que você pode aproveitar para carregar extensões de aplicativos.

Se você estiver interessado leia mais ...

BALKANGraphraph
fonte
Obrigado, parece interessante. Quão fácil é aplicar princípios semelhantes em outros idiomas?
Just #
Na verdade MEF é apenas para .NET framework Eu não sei nada sobre outros frameworks
BALKANGraph
7

Certa vez, trabalhei em um projeto que tinha que ser tão flexível na maneira como cada cliente podia configurar o sistema, que o único bom design que encontramos foi enviar ao cliente um compilador C #!

Se a especificação for preenchida com palavras como:

  • Flexível
  • Plugar
  • Customizável

Faça muitas perguntas sobre como você dará suporte ao sistema (e como o suporte será cobrado, pois cada cliente pensará que seu caso é o caso normal e não precisará de plug-ins.), Como na minha experiência

O suporte dos clientes (ou do pessoal da linha de suporte) escrevendo Plug-Ins é muito mais difícil do que a Arquitetura

Ian Ringrose
fonte
5

Na minha experiência, as duas melhores maneiras de criar uma arquitetura de plug-in flexível são as linguagens de script e as bibliotecas. Esses dois conceitos são ortogonais em minha mente; os dois podem ser misturados em qualquer proporção, como na programação funcional e orientada a objetos, mas encontram suas maiores forças quando equilibrados. Uma biblioteca é normalmente responsável por cumprir uma interface específica com funcionalidade dinâmica, enquanto os scripts tendem a enfatizar a funcionalidade com uma interface dinâmica.

Eu descobri que uma arquitetura baseada em scripts gerenciando bibliotecas parece funcionar melhor. A linguagem de script permite a manipulação de alto nível de bibliotecas de nível inferior, e as bibliotecas são liberadas de qualquer interface específica, deixando toda a interação no nível do aplicativo nas mãos mais flexíveis do sistema de script.

Para que isso funcione, o sistema de script deve ter uma API bastante robusta, com ganchos nos dados, na lógica e na GUI do aplicativo, além da funcionalidade básica de importação e execução de código das bibliotecas. Além disso, geralmente é necessário que os scripts sejam seguros, no sentido em que o aplicativo pode se recuperar normalmente de um script mal escrito. Usar um sistema de script como uma camada indireta significa que o aplicativo pode se destacar mais facilmente no caso de Something Bad ™.

O meio de empacotar plugins depende muito da preferência pessoal, mas você nunca pode dar errado com um arquivo compactado com uma interface simples, digamos PluginName.extno diretório raiz.

Jon Purdy
fonte
3

Eu acho que você precisa primeiro responder à pergunta: "Quais componentes devem ser plugins?" Você deseja manter esse número no mínimo absoluto ou o número de combinações que você deve testar explode. Tente separar seu produto principal (que não deve ter muita flexibilidade) da funcionalidade do plug-in.

Descobri que o diretor do IOC (Inversion of Control) (leia springframework) funciona bem para fornecer uma base flexível, à qual você pode adicionar especialização para simplificar o desenvolvimento de plugins.

  • Você pode verificar o contêiner em busca do mecanismo "interface como um anúncio do tipo plug-in".
  • Você pode usar o contêiner para injetar dependências comuns que os plug-ins podem exigir (por exemplo, ResourceLoaderAware ou MessageSourceAware).
Justin
fonte
1

O padrão de plug-in é um padrão de software para estender o comportamento de uma classe com uma interface limpa. Frequentemente, o comportamento das classes é estendido pela herança de classes, onde a classe derivada substitui alguns dos métodos virtuais da classe. Um problema com esta solução é que ela entra em conflito com a ocultação da implementação. Isso também leva a situações em que a classe derivada se torna um local de encontro de extensões de comportamento não relacionadas. Além disso, o script é usado para implementar esse padrão, como mencionado acima "Faça internals como uma linguagem de script e escreva todas as coisas de nível superior nessa linguagem. Isso a torna bastante modificável e praticamente à prova do futuro". Bibliotecas usam scripts para gerenciar bibliotecas. A linguagem de script permite manipulação de alto nível de bibliotecas de nível inferior. (Também como mencionado acima)

PresB4Me
fonte