Eu trabalho em um projeto que usa injeção de dependência (Spring) para literalmente tudo o que é uma dependência de uma classe. Estamos em um ponto em que o arquivo de configuração do Spring aumentou para cerca de 4000 linhas. Há pouco tempo, assisti a uma das palestras do tio Bob no YouTube (infelizmente, não consegui encontrar o link), em que ele recomenda injetar apenas algumas dependências centrais (por exemplo, fábricas, banco de dados, etc.) no componente Principal a partir do qual ser distribuído.
As vantagens dessa abordagem são a dissociação da estrutura DI da maior parte do aplicativo, além de tornar a configuração do Spring mais limpa, pois as fábricas conterão muito mais do que estava na configuração anterior. Pelo contrário, isso resultará na disseminação da lógica de criação entre muitas classes de fábrica e os testes poderão se tornar mais difíceis.
Então, minha pergunta é realmente quais são as outras vantagens ou desvantagens que você vê em uma ou outra abordagem. Existem práticas recomendadas? Muito obrigado por suas respostas!
fonte
Respostas:
Como sempre, depende. A resposta depende do problema que se está tentando resolver. Nesta resposta, tentarei abordar algumas forças motivadoras comuns:
Favorecer bases de código menores
Se você possui 4.000 linhas de código de configuração do Spring, suponho que a base de código tenha milhares de classes.
Não é um problema que você possa resolver após o fato, mas como regra geral, eu tendem a preferir aplicativos menores, com bases de código menores. Se você gosta de Design Orientado a Domínio , pode, por exemplo, criar uma base de código por contexto limitado.
Baseei esse conselho em minha experiência limitada, pois escrevi código de linha de negócios baseado na Web durante a maior parte da minha carreira. Eu poderia imaginar que, se você estiver desenvolvendo um aplicativo de desktop, um sistema incorporado ou outro, é mais difícil separar as coisas.
Embora perceba que esse primeiro conselho é facilmente o menos prático, também acredito que é o mais importante, e é por isso que o incluo. A complexidade do código varia de maneira não linear (possivelmente exponencialmente) com o tamanho da base de código.
Favor Pure DI
Embora eu ainda perceba que esta pergunta apresenta uma situação existente, recomendo o Pure DI . Não use um contêiner de DI, mas se o fizer, pelo menos use-o para implementar a composição baseada em convenções .
Não tenho nenhuma experiência prática com o Spring, mas estou assumindo que, pelo arquivo de configuração , um arquivo XML está implícito.
Configurar dependências usando XML é o pior dos dois mundos. Primeiro, você perde a segurança do tipo em tempo de compilação, mas não ganha nada. Um arquivo de configuração XML pode facilmente ser do tamanho do código que ele tenta substituir.
Comparado ao problema que ele pretende resolver, os arquivos de configuração da injeção de dependência ocupam o lugar errado no relógio de complexidade da configuração .
O caso da injeção de dependência de granulação grossa
Eu posso defender uma injeção de dependência de granulação grossa. Também posso defender a injeção de dependência refinada (consulte a próxima seção).
Se você injetar apenas algumas dependências 'centrais', a maioria das classes será semelhante a:
Isso ainda se encaixa na composição de objetos a favor dos Design Patterns sobre a herança de classe , porque
Foo
compõeBar
. De uma perspectiva de manutenção, isso ainda pode ser considerado sustentável, porque se você precisar alterar a composição, basta editar o código-fonteFoo
.Isso é dificilmente menos sustentável do que a injeção de dependência. Na verdade, eu diria que é mais fácil editar diretamente a classe que usa
Bar
, em vez de seguir o indireto inerente à injeção de dependência.Na primeira edição do meu livro sobre injeção de dependência , faço a distinção entre dependências voláteis e estáveis.
Dependências voláteis são aquelas dependências que você deve considerar injetar. Eles incluem
Dependências estáveis, por outro lado, são dependências que se comportam de maneira bem definida. Em certo sentido, você poderia argumentar que essa distinção justifica a injeção de dependência de granulação grossa, embora deva admitir que não percebi totalmente isso quando escrevi o livro.
De uma perspectiva de teste, no entanto, isso dificulta o teste de unidade. Você não pode mais testar a unidade
Foo
independentementeBar
. Como JB Rainsberger explica , os testes de integração sofrem com uma explosão combinatória de complexidade. Você literalmente precisará escrever dezenas de milhares de casos de teste se quiser cobrir todos os caminhos por meio de uma integração de até 4-5 classes.O contra-argumento disso é que, freqüentemente, sua tarefa não é programar uma classe. Sua tarefa é desenvolver um sistema que resolva alguns problemas específicos. Essa é a motivação por trás do Behavior-Driven Development (BDD).
Outra visão sobre isso é apresentada por DHH, que afirma que o TDD leva a danos no projeto induzidos pelo teste . Ele também é a favor de testes de integração de granulação grossa.
Se você adota essa perspectiva no desenvolvimento de software, a injeção de dependência de granulação grossa faz sentido.
O caso da injeção de dependência refinada
A injeção de dependência refinada, por outro lado, pode ser descrita como injetar todas as coisas!
Minha principal preocupação em relação à injeção de dependência de granulação grossa é a crítica expressa por JB Rainsberger. Você não pode cobrir todos os caminhos de código com testes de integração, porque você precisa escrever literalmente milhares ou dezenas de milhares de casos de teste para cobrir todos os caminhos de código.
Os proponentes do BDD vão contra o argumento de que você não precisa cobrir todos os caminhos de código com testes. Você só precisa cobrir aqueles que produzem valor comercial.
Na minha experiência, no entanto, todos os caminhos de código 'exóticos' também serão executados em uma implantação de alto volume e, se não testados, muitos deles terão defeitos e causarão exceções em tempo de execução (geralmente exceções de referência nula).
Isso me levou a favorecer a injeção de dependência refinada, porque me permite testar os invariantes de todos os objetos isoladamente.
Favorecer a programação funcional
Enquanto eu me inclino para a injeção de dependência refinada, mudei minha ênfase para a programação funcional, entre outros motivos, porque é intrinsecamente testável .
Quanto mais você avançar para o código SOLID, mais funcional ele se tornará . Mais cedo ou mais tarde, você também pode mergulhar. A arquitetura funcional é a arquitetura de portas e adaptadores e a injeção de dependência também é uma tentativa e as portas e adaptadores . A diferença, no entanto, é que uma linguagem como Haskell impõe essa arquitetura por meio de seu sistema de tipos.
Favorecer a programação funcional digitada estaticamente
Neste ponto, desisti essencialmente da programação orientada a objetos (OOP), embora muitos dos problemas da OOP estejam intrinsecamente acoplados a linguagens comuns, como Java e C #, mais do que o próprio conceito.
O problema com as principais linguagens OOP é que é quase impossível evitar o problema de explosão combinatória, que, não testado, leva a exceções em tempo de execução. Linguagens de tipo estático, como Haskell e F #, por outro lado, permitem codificar muitos pontos de decisão no sistema de tipos. Isso significa que, em vez de ter que escrever milhares de testes, o compilador simplesmente dirá se você lidou com todos os caminhos de código possíveis (até certo ponto; não é uma bala de prata).
Além disso, a injeção de dependência não é funcional . A verdadeira programação funcional deve rejeitar toda a noção de dependências . O resultado é um código mais simples.
Sumário
Se forçado a trabalhar com C #, prefiro a injeção de dependência refinada, pois me permite cobrir toda a base de código com um número gerenciável de casos de teste.
No final, minha motivação é um feedback rápido. Ainda assim, o teste de unidade não é a única maneira de obter feedback .
fonte
Grandes classes de configuração de DI são um problema. Mas pense no código que ele substitui.
Você precisa instanciar todos esses serviços e outros enfeites. O código para fazer isso seria no
app.main()
ponto de partida e injetado manualmente, ou fortemente acoplado comothis.myService = new MyService();
dentro das classes.Reduza o tamanho da sua classe de instalação dividindo-a em várias classes de instalação e chame-as a partir do ponto inicial do programa. ie
Você não precisa fazer referência ao service1 ou 2, exceto para passá-los para outros métodos de configuração da ci.
É melhor do que tentar obtê-los do contêiner, uma vez que impõe a ordem de chamar as configurações.
fonte
ci
variável é essa ?