Finalmente trago o Spring.NET em seu fork Oragon.Spring para o .NET Standard 2.1 e ASP.NET Core 3.0.
Nos últimos anos tenho me dedicado a falar mais de arquitetura de solução do que arquitetura de software em si. Cada vez que peso os estudos em uma das duas direções, me afasto da outra. E assim vamos pendendo ou para um lado ou para o outro. Com a evolução natural dos dois skills, manter-me conectado às duas vertentes é um esforço diário. Mas é hora de voltar a falar de software e Spring tem muito a ver com esse assunto.
Spring .NET é um projeto que me acompanha a mais de uma década. Já falei sobre isso em algumas oportunidades:
- Oragon.Spring no ASP.NET Core – Get Started Tutorial (PT-BR)
- Novidades – Q3/2018 – Oragon Spring.NET, AOP, Open ALM
- Oragon.Spring
- Spring.NET o Renascimento
A propósito, estou falando do Spring .NET, versão do Spring do java, para .NET.
Eu acredito que a comunidade .NET deve muito de sua maturidade aos acertos e tropeços de Java.
Sem essa "guerra" não poderíamos evoluir e nunca num espaço de tempo tão curto.
Esse é o tipo do projeto em que rolou paixão à primeira vista. Se você tem interesse em Clean Architecture e Clean Code, vale a pena olhar as releases de 2006 e 2007 do projeto. Principalmente a release que trouxe para o projeto o Spring.Services.
Essa release e principalmente o Spring.Services não fazem muito sentido hoje. Principalmente com o fim do WCF, o fim do Enterprise Services, .NET Remoting e soluções mais recentes ao redor do Kestrel como a nova implementação de gRPC provida pela Microsoft. Mas em uma época onde Enterprise Library era default, Spring .NET aparece como algo revolucionário.
Embora como toda solução que entrega código seja, à primeira vista enxergada meramente como asset, o real valor do Spring sempre esteve em sua visão não opinativa, deixando-o livre para uma modelagem mais limpa, mais clean, pura.
Seu modelo de configuração via XML, totalmente isolado do código e conectável a qualquer código, produzia possibilidades infinitas. O ponto é que você como desenvolvedor, pode tomar decisões e sim ser opinativo a respeito de como usar ou como lidar com a sua modelagem, o que quero ressaltar é que você tem esse poder e talvez até esse dever, mas o Spring, enquanto container de Inversão de Controle e Injeção de Dependência, não era opinativo. Ele sempre foi rico o suficiente para que você tivesse controle sobre suas abstrações, e sua própria modelagem.
A falta de "facilitadores para gambiarras" e convenções malucas, também ajudam a deixar claro, como cada coisa funciona. Preto no branco! Gambiarras não são built-in.
Sem jeitinho, sem malandragem.
100% declarativo, permitindo detalhar e expressar cada grafo de objetos, com suas dependências, com suas configurações. Essas capacidades são únicas na forma como ele deixa de atrapalhar seu design.
Posts como ASP.NET CORE DEPENDENCY INJECTION – REGISTERING MULTIPLE IMPLEMENTATIONS OF AN INTERFACE nunca existiram a respeito do Spring .NET, simplesmente pois isso não é uma questão. Somente Register/Resolvers possuem esse tipo de questão. Eu já falei disso algumas vezes aqui:
O poder da Inversão de Controle e Injeção de Dependência
Inversão de Controle e Injeção de Dependência são poderosíssimos, mas pouca gente se dá conta disso.
A propósito, eu, sinceramente sou muito a favor de substituir o acrônimo SOLID por SOLIDI. Onde o último I é o lugar de direito de Inversion Of Control (inversão de controle).
SOLID e IoC podem fazer coisas incríveis, dependendo das escolhas tecnológicas que você faz.
🏆 Substituir uma dependência local, por uma remota, com segurança e resiliência? Sim, é possível.
🏆 Evitar a construção de repositórios de configuração, mantendo instâncias estativamente configuradas. Sim, fácil.
🏆 Realizar o consumo de filas AMQP simplesmente conectando uma fila a um método de um serviço, sem necessidade desse serviço ter nenhuma referência a mensageria. Possibilitando que esse mesmo método possa receber uma requisição oriunda de diversas plataformas, tecnologias, em nenhum código adicional para isso? Sim, eu fiz isso diversas vezes.
Todos os itens acima são itens que consistentemente e recorrentemente tenho feito usando Spring .NET, agora o Oragon Spring. Mas o mais importante é que não tem a ver com o Spring. O Spring simplesmente me apresentou esse modelo, essa estratégia de modelar, lá em 2007.
Qualquer framework de IoC e DI em conjunto com outras soluções, viabilizam com mais engenharia, ou com menos engenharia, a criação de soluções de fato robustas. E isso não tem a ver com Monolitos ou Microsserviços, tem a ver com modelagem do que você quiser.
Embora essas afirmações a seguir não estejam solidificadas nem claras o suficiente na minha cabeça, suspeito que Microsserviços possam pecar em alguns desses requisitos, por serem fragmentos que talvez possam ter algum acoplamento tecnológico. Não estou certo. Não sei se aqui, seja ou não encarado como overengineering. Não estou certo sobre a necessidade de um microsserviço ser agnóstico. Eu falei sobre Agnostic Services em Oragon Design Guide – Agnostic Services.
A chegada do projeto ao ASP.NET Core 3.0
Esse segundo semestre está sendo bem agitado, e uma das necessidades que tenho se encaixa muito bem na forma como o Spring me permite modelar. A migração para o ASP.NET Core já havia começado, mas precisava terminar. Algumas quebras conceituais chatas e isso definitivamente me irritou um pouco.
Infelizmente com as convenções realizadas na obtenção de dependências pelo ServiceProvider, reimplementá-las no Oragon.Spring é algo inviável nas próximas semanas. Ou eu escolhia manter o projeto compatível com .NET Core 2.2 ou faria alguma coisa para conseguir migrar para o 3.0.
A migração aconteceu de forma semelhante ao que existia no .NET Core 2.2. O container de inversão de controle está operando como fallback do service provider default. Esse efeito só é possível ser percebido em aplicações ASP.NET. Já que em aplicações .NET Core o comportamento continua o mesmo de sempre.
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureOragonSpring(options => { options.MakeBridgeFor<IConfiguration>("Configuration"); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
Na classe Program, temos o método ConfigureOragonSpring() que por sua vez permite configurar a integração com o Oragon Spring.
São poucas configurações:
AddConfigLocation
Permite sobrescrever o path do XML de configuração do Spring, permitindo inclusive usar vários XML's.
A propósito, caso você não chame esse método, a infraestrutura usará o path default para o XML, que mudou. O path default era .AppContext.xml e agora é .Oragon.Spring.xml.
Da forma como está, o arquivo é copiado para o disco, mas esse path permite usar as URI's de assemblies .NET, para lidar com embedded resources.
MakeBridgeFor
A segunda configuração possível trata-se de copiar uma referência do Service Provider padrão, para o container Oragon.Spring, permitindo fazer uma ponte e referenciar coisas como IConfiguration entre ou o host do ASP.NET ou alguma outra coisa da infraestrutura.
No exemplo options.MakeBridgeFor<IConfiguration>("Configuration"), estamos criando um object com name Configuration no container Oragon.Spring, a instância é obtida do ServiceProvider padrão para que essa referência possa ser usada no Oragon.Spring. O serviço DummyService utiliza essa dependência para realizar seu trabalho.
Exemplos:
ASP.NET Core 3.0
Aqui está nosso exemplo ASP.NET Core 3.0 | https://github.com/Oragon/Oragon.Spring/tree/master/Oragon.Spring.Core.AspNetCoreTest
.NET Core 3.0
Aqui está nosso exemplo .NET Core 3.0 (console) | https://github.com/Oragon/Oragon.Spring/tree/master/Oragon.Spring.Core.ConsoleTest
Decision Log
Já na versão 2.2 do .NET Core eu não estava satisfeito e nem disposto a manter a implementação com 2 service providers. Mas já havia sinais claros de que a infraestrutura de injeção de dependência mudaria na versão seguinte. Adiei a implementação adequada para a versão 3.0.
Ao começar o processo de criação de um Service Provider Spring, encontrei comportamentos pra mim inesperados.
Convenção para IEnumerable<T>
Se você tem um tipo que depende de um IEnumerable<T>. Você não precisa registrar o IEnumerable<T>. Basta registrar diversas implementações para T.
Múltiplas instâncias de um tipo não geram problema
Seguindo a mesma linha, se minha classe depende de T, mas no container, temos diversas instâncias de T. O que eu esperava era uma exception, algo como uma InvalidOperationException. Mas não. O service provider infere e vai te entregar uma instância (acredito que a primeira ou a última).
Interferência indesejada
Eu não esperava esses comportamentos. Esperava que se quisesse algo assim, eu deveria setar alguma coisa, registar algum tipo de resolver que pudesse avaliar as configurações ou instâncias para tomar sua decisão. Mas definitivamente eu não esperava esse tipo de comportamento, novamente opinativo.
Pra que? Qual o sentido.
Agora temos na documentação oficial uma explicação a respeito do assunto. Como apresentado na imagem acima.
Meu principal ponto é que essa nova geração de containers são ótimas introduções à IoC e DI, mas sinceramente não consigo encontrar amparo para o uso inteligente de IoC e DI. Eu não me conformo com essa permissividade, que naturalmente gera problemas na modelagem, como é o caso do HttpClientFactory onde citei esse problema.
Onde encontrar?
Como descrito em Underwater – Construindo Libraries .NET Standard Profissionais, as versões Alpha vão para o Myyget.
O feed alpha está disponível https://www.myget.org/F/oragon/api/v3/index.json. Você pode usar o Nuget Package Explorer para vê-lo.
Pela imagem acabei de descobrir que todos os pacotes precisam de descrições melhores! 🤬 🤬 🤬
Os feeds Beta e GA são disponibilizados no nuget.org.
A propósito, em breve eu abordarei Nuget Package Explorer e dotPeek e ILSpy.
Solidi soma com mais uma boa prática, interessante.