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

Análise vs. Projeto

Eu diria que a ruptura entre Análise e o Projeto acontece quando acabam as considerações sobre o problema e começam as considerações sobre a solução. Em outras palavras, o papel do Analista é considerar o problema, enquanto o papel do Projetista é o de considerar a solução. Assim, o Analista aparece quando um problema aparece, e dá lugar ao Projetista quando o problema foi devidamente analisado, restando produzir para ele uma solução. E diria que a ruptura entre a Especificação e a Implementação acontece quando acabam os artefatos para consumo por humanos e começam os artefatos para consumo por máquinas. Em outras palavras, o papel do Especificador é produzir artefatos para consumo por humanos, enquanto o papel do Implementador é produzir artefatos para consumo por máquinas. Assim, o Especificador aparece quando a atividade é produzir efeitos sobre humanos, e dá lugar ao Implementador quando os humanos estão devidamente satisfeitos e a atividade prossegue para produzir efeito...

Por que goto é considerado prejudicial?

Recentemente, o Fabiano Vasconcelos abriu uma discussão no Grupo de Usuários de C e C++: De cara eu vi algo aqui um pouco estranho, se que o amigo Márcio me permite comentar: que muitos programadores, inclusive eu (se que posso ser rotulado como programador) foram instruídos com o princípio de NUNCA usar o goto, por ser considerado um mau estilo de programação. Durante a discussão, o Eduardo Vieira puxou um artigo da KernelTrap sobre uma discussão similar ocorrida no grupo de desenvolvimento do Linux, onde Robert Wilken disse o seguinte: In general, if you can structure your code properly, you should never need a goto, and if you don't need a goto you shouldn't use it. It's just "common sense" as I've always been taught. Unless you're intentionally trying to write code that's harder for others to read. É preciso colocar a máxima "goto considered harmful" na perspectiva histórica adequada. Acredito que praticamente todo programador trein...

Servidor e usuário COM na mesma "solution"

 Um dos objetivos da próxima iteração no meu projeto atual é quebrar o sistema em mais pedaços, e levantar os pedaços um nível de abstração até componentes COM. Uma das consequências é substituir diversas ligações mais estáticas e torná-las ligações mais dinâmicas através do COM. Tipicamente, um programa usuário de um componente COM, escrito para o Visual Studio, usará a diretiva #import para incluir na unidade de tradução C++ as declarações necessárias. Isso implica que o componente deve estar instalado na máquina que constrói o programa. O que fazer quando o componente em questão está no mesmo lote de construção que o programa usuário? A solução inicial é criar uma dependência de construção tal que o componente seja sempre construído primeiro, e usar um post-build event ao projeto do componente para registrá-lo no sistema. Desse modo, na construção do programa, o ambiente estará correto. Eu considero este arranjo prejudicial. Minha melhor razão é esta: o servidor de const...