Pular para o conteúdo principal

Invertendo a direção das dependências

Hoje, mais uma vez, resolvi um problema de projeto virando o jogo de cabeça para baixo.

Pretendo refazer este artigo no futuro com uns diagramas mais legais; por enquanto segue apenas o texto para não ficar esquecido.

Um sistema extensível exibe padrões conhecidos; por toda parte vemos sistemas de "plugins".

Imaginando nosso sistema com "plugins", vemos um elemento Central e diversos elementos Adicionais que podem ser inseridos e removidos do sistema à vontade. O elemento Central é um só e há vários Adicionais ligados ao Central.

Extraindo dessa imagem uma imagem conceitual, vemos o tipo Central em relação com o tipo Adicional, onde Adicional tem multiplicidade zero ou mais.

Partindo daqui para uma imagem comportamental, vemos o que acontece quando inserimos o Adicional. Ou tentamos ver. O que acontece? Quem faz o quê?

Vamos supor que estamos criando tudo isso com um servidor de aplicação J2EE. Nesse ambiente, os módulos, quando iniciados, sofrem (1) injeção de dependências em @EJB etc., (2) chamadas a @PostConstruct.

Acompanhando nossa imagem conceitual, onde Central têm Adicionais, buscamos entender como Central pode fazer alguma coisa a cada vez que um Adicional é inserido e iniciado no sistema de módulos.

Se nós temos um sistema com resolução e injeção automática de depenências, declararíamos que o Central tem dependência nos Adicionais, com o objetivo de causar o sistema de módulos injetar Adicionais no Central sempre que forem iniciados, e o inverso quando forem removidos.

Como acontece isso? O sistema de módulos ativar uma rotina qualquer de Central a cada vez que um Adicional for iniciado -- uma rotina addDependency por exemplo. O oposto aconteceria quando o Adicional é removido. A esperança é que Central receba várias chamadas a addDependency, cada uma das vezes recebendo uma referência ao Adicional que iniciou.

Certo, mas o que acontece quando se faz necessário reiniciar o módulo Central? Todos os módulos Adicionais permanecem iniciados, o que é muito interessante se estes módulos mantém um estado importante; também é um desperdício finalizar e reiniciar módulos à toa. Quando o módulo Central inicia após parar, o sistema de módulos fará o quê? Deve redescobrir a presença de todos os módulos já iniciados? A linguagem usada até agora indica que os Adicionais prévios serão "perdidos", já que addDependency executa apenas quando Adicionais são iniciados.

Se o sistema de módulos concreto usado pelo projeto não fora capaz de fazer isso, qual é a alternativa? Talvez enumerar todos os módulos Adicionais em um arquivo de configuração, atividade que deve ser realizada a cada vez que um Adicional for inserido ou removido do sistema, tal que o módulo Central possa recarregar a configuração e subsequentemente carregar e descarregar Adicionais de acordo.

Isso está ficando um pouco sacal.

Felizmente, para tornar o efeito desejado muitíssimo mais razoável, é preciso apenas inverter a relação de dependência: o Central não depende do Adicional, mas sim o Adicional depende do Central.

Quando um Adicional for iniciado no sistema, este não chamará o Central para fazer algo; o sistema de módulos chamará o próprio Adicional para fazer o que ele acha que deve fazer.


Ao iniciar um Adicional, o sistema de módulos não injetará o Adicional no Central como algum tipo de dependência, mas o contrário: injetará o Central como dependência do Adicional.

Esta relação é simples, já que apenas um Central existe para todos os Adicionais.

Se os Adicionais conhecem o Central através de uma interface útil -- e com certeza deve haver alguma relação entre os módulos, já que todo Adicional deve oferecer um interface útil em contra-partida -- então os Adicionais podem registrar e desregistrar a si mesmos.

Quando o Adicional inicia no sistema, é natural que o sistema cuide de satisfazer suas dependências; se o Central não está lá, o Adicional não pode subir, o que é óbvio; se o Central está lá previamente, deve ser configurado no Adicional. Se o Adicional é reiniciado, o óbvio acontece; se o Central é reiniciado, todos os Adicionais que dependem dele devem ser interrompidos conjuntamente devido a remoção do dependente, o que é razoável, e reiniciados depois quando a dependência se torna satisfeita, o que também é razoável.

O mesmo efeito se obtém no OSGi e em sistema de módulos similares.

É incrível como essa noção é alienígena ao raciocínio do projetista; para o meu raciocínio e o de projetistas conhecidos por mim. É preciso fazer esforço para imaginar um fluxo de trabalho onde o subordinado é quem pilota o gerente, e não o contrário.

Comentários

Postagens mais visitadas deste blog

return void();

É uma pequeneza mas eu gostaria muito que as linguagens da família do C permitissem retornar void quando a função retorna void . Escrever da forma a seguir me aborrece. public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null)      {        log("x was null");        return;      }      bar(x, "default");    } } Eu ficaria mais feliz escrevendo assim: public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null) return log("x was null");      bar(x, "default");     } }

.NET COM Interop, IStream e Visual Studio

Investigando oportunidades para otimizar um componente, experimentei definir métodos que não solicitam dados (em arrays etc.) como argumentos mas sim instâncias de IStream , e me deparei com um problema interessante: interoperabilidade com .NET. Usar componentes COM em programas .NET não é particularmente difícil; no Visual Studio, é possível adicionar componentes COM como referências no projeto. Se o componente for capaz de Automation, é possível usar a ferramenta tlbimp.exe para gerar manualmente um callable wrapper para o componente, que, se eu entendi as coisas corretamente, não precisa ser distribuído com a aplicação. Porém, todos os métodos que eu aprendi tem essa peculiaridade: eles parecem assumir que um componente não usa interfaces definidas por outros componentes -- como, por exemplo, declarando parâmetros com tipo IStream , uma interface definida pelo sistema. Pelo método do callable wrapper , a ferramenta assume que as interfaces mencionadas integram o componente...

Eclipse PDE com Gradle

Na virada de 2016 para 2017, adotamos oficialmente o Gradle como ferramenta de construção de software na Prodist. A fila da migração está fluindo confortavelmente desde então. Já temos as provas de conceito necessárias para todos os projetos, inclusive os projetos nativos para compilador C++. Para fontes Java usamos Eclipse. Integramos projetos Gradle ao Eclipse usando Buildship . Acho que o Buildship ainda tem um longo caminho pela frente, mas realiza tudo o que necessitamos. Já estamos entregando com base nesse esquema. O problema mais importante que encontramos foi na integração de projetos OSGi. No Eclipse, trabalhamos projetos OSGi com o PDE. Nosso ciclo de desenvolvimento inclui rodar configurações "OSGi Framework" no Eclipse para teste. Infelizmente, o PDE assume uma estrutura de projeto própria e não funciona corretamente caso contrário. Estes problemas estão registrados em bugs como, por exemplo, o  bug 153023 . Nossos projetos OSGi com Gradle usam o layout ...