Quem me acompanha, principalmente já viu ou participou de alguma solução minha na última década, sabe que o Spring.NET é meu fiel escudeiro. Há motivos de sobra para não me desapegar do projeto, no entanto recorrentemente testo novas alternativas. Entretanto, mais de uma década após os primeiros flertes, ainda é meu container favorito, e por isso merece uma menção honrosa não só no blog, mas na minha carreira como um todo. Muito do que fiz com .NET nos últimos anos não seria economicamente viável sem ele.
O que vem a ser Spring.NET?
O Spring é um conceituado framework de aplicação do Java suportado e mantido pela SpringSource. A versão .NET do framework não é um port simples, como eles mesmos chamam, é um port espiritual. A diferença mora no reaproveitamento de código entre as baselines: Simplesmente não há! Inspirado no Java, Mark Pollack, seu fundador, criou toda a baseline de código do zero, sem migração direta, o que ajuda na compreensão de que é um framework .NET inspirado em um caso de sucesso do Java.
Embora seja composto por mais de 15 módulos, como mostra a imagem abaixo. Vamos abordar os dois principais módulos que dão suporte a todos os demais.
O projeto conta com módulos para Acesso a Dados(Nhibernate, ADO, etc), Mensageria (MSMQ, AMQP, Tibco, etc), Serviços (Enterprise Services, Web Services, Remoting, Wcf), MVC (Da versão 1 a 5), além do suporte a Agendamentos com Quartz.NET e Testes com NUnit e MSTest.
As categorias que encontramos são: Core, Data Access, Web, Services, Integration, você pode encontrar a lista completa na documentação oficial, essa lista de módulos é baseada em 2 módulos principais: Spring.Core e Spring.AOP. É sobre esses dois módulos que irei discorrer nesse post.
Spring.Core
O Spring.Core é o módulo principal onde reside o Container IoC, e toda a infraestrutura de Injeção de Dependência, hooks e abstrações que dão suporte aos demais módulos. É o coração do projeto no que diz respeito à IoC e DI. É um rico substituto para o Ninject, Castle Windsor, StructureMap e demais containers da lista.
Spring.AOP
O Spring.AOP enriquece o Spring.Core, trazendo consigo a infraestrutura de AOP, baseada no AopAlliance, possibilitando que todo o processo de configuração de AOP seja feito direto no container, reduzindo o acoplamento e necessidade de redesign ou interferência no design. Com o mínimo de respeito ao SOLID, é possível utilizar infraestrutura de IoC, DI e AOP sem que haja necessidade de recompilar nada, criando um leque de possibilidades infinitas.
O que o torna diferente?
Na prática se você sabe o que é um container IoC / DI e faz ideia do que um container faz, sabe que não é tarefa fácil. Desde a gestão do grafo de dependências, pipeline de inicialização, etc. Há diversos elementos complexos envolvendo todas essas tarefas, mas se estivermos falando de implementações simplistas, aí sim temos um pipeline mais curto mesmo.
Enquanto escrevia esse post, revisei as principais implementações de containers e me surpreendi positivamente. Da época em que eu fiz os posts IoC e Dependency Injection – Os erros comuns e IOC / DI – Você está fazendo isso errado! reclamando das principais implementações até hoje, muita coisa mudou. Os principais containers já oferecem formas de tornar a injeção de uma dependência eventualmente declarativa, e alguns vão além, possibilitando a definição de dependências nomeadas e padrão por tipo, o que é ótimo por atender as 2 necessidades.
Configuração
O Spring.NET traz consigo a flexibilidade de um modelo de configuração 100% baseado em XML’s. Um inferno para muitos, adorado por outros tantos. O modelo de configuração baseado em XML, oferece uma abstração nítida e uma visão clara de como cada instância está sendo criada. Os elementos, explícitos, apresentando classes, propriedades e construtores, cria uma imersão sem igual, permitindo encará-lo como um repositório estático de instâncias configuradas. Esse viés, faz com que objetos muito complexos, possam ser criados sem muito esforço, exigindo apenas configuração adequada.
Exemplo 1
A criação de um objeto, sem preencher nenhuma propriedade, utilizando construtor default (não parametrizado).
<object id="contextListenerObject" type="Spring.Context.ContextListenerObject, Spring.Core.Tests"/>
Exemplo 2
Uma configuração de uma instância de Spring.Objects.TestObject, que está no assembly Spring.Core.Tests, onde estamos criando essa instância utilizando o construtor de 2 parâmetros: Name e Age, respectivamente preenchidos com Rick e 30.
<object id="objectWithCtorArgValueShortcuts" type="Spring.Objects.TestObject, Spring.Core.Tests" singleton="false"> <constructor-arg name="name" type="string" value="Rick" /> <constructor-arg name="age" type="int" value="30" /> </object>
Exemplo 3
Semelhante ao exemplo 2, mas agora utilizando utilizando o construtor default, sem parâmetros, e preenchimento as propriedades com os mesmos valores.
<object id="testObject" type="Spring.Objects.TestObject, Spring.Core.Tests"> <property name="Name" value="Rick"/> <property name="Age" value="30"/> </object>
Exemplo 4
Eu não vou pesar e mostrar algo muito complexo, mas aqui vai um exemplo intermediário, com referências (ref=”…”), com objetos aninhados (QueueTransition.ConsumerCountManager está sendo preenchido com um ConsumerCountmanager, que por sua vez está sendo criado com o construtor não parametrizado, mas suas propriedades estão sendo preenchidas.
<object type="DevWeek.Architecture.Workflow.QueuedWorkFlow.QueuedTransition, DevWeek.Services"> <property name="Origin" value="RequestStored" /> <property name="Destination" value="MetadataDownloaded" /> <property name="LogicalQueueName" value="MetadataDownloader" /> <property name="ExchangeName" ref="CONFIG:DevWeek:RabbitMQ:DownloadPipeline:Exchange" /> <property name="ConsumerCountManager" > <object type="DevWeek.Architecture.MessageQueuing.ConsumerCountManager, DevWeek.Services"> <property name="MinConcurrentConsumers" value="1" /> <property name="MaxConcurrentConsumers" value="10" /> <property name="AutoscaleFrequency" value="00:01:00" /> <property name="MessagesPerConsumerWorkerRatio" value="1" /> </object> </property> <property name="ServiceMethod" value="ExecuteAsync" /> <property name="Service"> <object type="DevWeek.Services.Downloader.MetadataDiscoveryPipelineActivity, DevWeek.Services" autowire="constructor"></object> </property> <property name="ErrorFlowStrategy" value="SendToErrorQueue" /> </object>
Esse modelo desacoplado permite que você faça coisas incríveis, desconectado do seu domínio.
Sobre o port
Spring é mantido pela SpringSource, no entanto não há muito empenho no github já faz algum tempo. Questões como “spring.net is dead” são recorrente nos fóruns então resolvi fazer alguma coisa. Antes de ver o projeto morrer, pois aparentemente está em coma profundo. Resolvi fazer um fork e trabalhar na migração AS-IS para .NET Core. A dinâmica adotada é simplista: Recriar os principais projetos ignorando dependências não portadas, para essas vamos criar fakes. A principal meta era garantir a completude da migração, sem mudar nada no código do projeto. Mantendo o que já estava funcionando, e garantindo, com fakes/mocks que o projeto funcionaria. Na medida que conseguisse compilar o projeto com .NET Standard, revisitaria os fakes, para somente então tomar decisões mais profundas, que provavelmente mudam características do projeto. Mas o mindset é sempre fazer modificações ao redor do projeto, evitando contundência nas alterações do core sem que antes se tenha um baseline de testes extenso, para dar suporte.
Fakes
Assim comecei a migração dos projetos Spring.Core e Spring.Aop e obtive sucesso em menos de 6 horas de esforço. Mocks/Fakes foram criados para dar suporte a dependência de Common.Logging, System.EnterpriseServices e System.Runtime.Remoting. Hoje, Common.Logging já está presente no .NET Core via .NET Standard, portanto pude simplesmente excluir meu fake, adicionar a dependência do pacote nuget, e o projeto voltou a usar o Common Logging.
Quer entender melhor o que é um fake? Olhe isso. Um fake é uma implementação falsa de algo. Em vez de ter o comportamento esperado, o fake, assim como um mock, garante entradas e saídas. A utilização de fakes me permite não precisar mudar sequer uma linha no código do projeto alvo. Isso acontece por que o projeto a ser migrado depende de alguém que não está disponível na nova tecnologia/plataforma. É aí que mora o problema: Abstrair, substituir/refatorar ou desistir?
Muitas vezes não conseguimos ter a visão do esforço total pois esbarramos em uma ou outra dependência. No caso do spring .net o ponto de maior fricção nos 2 projetos que me dispus a migrar foi o Common.Logging. Mas não era possível saber o que mais iria apresentar problemas decorrentes da mudança do .NET Framework para .NET Core. O empenho de fakes me permitiu percorrer todo o caminho deixando uma migalha de pão, consistente de forma a tornar claro quais eram as dependências críticas. Aqui está o primeiro fake do Common.Logging, que já foi removido do projeto, na medida que a equipe do projeto concluiu seu port para .NET Standard.
Era possível mudar a implementação desse fake para que utilizasse a infraestrutura de logging do .net core. Eu não optei por isso. Mas se o fizesse, aconteceria de forma precisa e central, transformando o fake em um wrapper ou um adapter, criaria uma issue, para que outro momento removêssemos definitivamente o fake.
Já nos 2 projetos de teste, foi necessário recorrer ao nsubstitute para conseguir entregar funcionalidades compatíveis às entregues pelo Rhino.Mocks. Outro ponto curioso desse job foi que a migração dos 2 projetos de teste foi imensamente mais custosa do que a migração dos respectivos projetos principais.
O mindset
Migrar somente o Spring.Core e Spring.Aop viabiliza a utilização do Spring.NET. Todos os demais pacotes e projetos podem ser repensados, e na minha visão, não é responsabilidade de quem mantém o Spring.NET. Acredito que o Spring .NET possa se ater a esses 2 projetos, e ainda mais, acredito que Spring.Aop possa ser incorporado ao Spring.Core. Acredito também que com a ausência desses projetos adicionais, menos usados, a comunidade se engaje para lançar projetos similares, compondo e dando vida ao Spring .NET. Não vejo com bons olhos o projeto principal ser responsável, também, por gerenciar integrações com os mais diversos frameworks, como NH, EF, MVC/WEBAPI etc. Não acredito nesse big elephant, definitivamente não é algo que enxergue com bons olhos.
Conclusão
Por fim, estou retomando o projeto, com esse escopo reduzido. Hoje os testes já foram migrados, no entanto a dependência do Rhino.Mocks me fez criar um fake que na prática consiste em um wrapper/adapter para o NSubstitute. Nesse momento estou evitando fazer mudanças no core, mas por uma questão de sanidade.
O projeto seguirá, vou trazer mais coisas do projeto original, como documentação, por exemplo. E conto com a ajuda de quem quiser participar. Vale a pena ressaltar que para isso é necessário concordar e estar comprometido com a estratégia, além de saber muito bem o que se está fazendo.
Bom, por hoje é só….
[youtube https://www.youtube.com/watch?v=qwmoZTljVN0&w=320&h=192]
Tem algum projeto Git teu com um exemplo simple com o spring?
Bruno, no github novo do projeto tem um exemplo sim.
O link Oragon.Spring/Oragon.Spring.Core.ConsoleTest cai direto nele! 😀
Depois de testar, diga o que achou?!
Enviei um PR com documentação do exemplo com asp.net core e criei um novo arquivo dockerfile utilizando as imagens alpine do dote core.
Segue o link: https://github.com/Oragon/Oragon.Spring/pull/2
Espero que esteja ok, pois é minha primeira contribuição com projetos open source.
Fabio, já recebi, estou entendendo como receber um PR. Para mim também é o primeiro recebido.