Do Java ao .NET – Parte 2: Arquitetura

Do Java ao .NET - Parte 2: Arquitetura 1

Então você desenvolve em Java e precisa aprender .NET? Estou na mesma situação: acompanhe estes posts pra que juntos dominemos o .NET. Neste post vamos entender a arquitetura fundamental do .NET. Bora lá?

No post anterior expus as diferentes implementações do .NET que existem hoje (ao menos as mais populares). Apesar de serem várias implementações todas possuem elementos em comum, que serão tratados neste post. Como verá são elementos análogos aos que temos no Java, o que torna a sua compreensão algo muito mais tranquilo por nós.

Neste post vou apresentar a arquitetura essencial da plataforma .net e alguns detalhes relacionados à execução do código que chamaram minha atenção.

Importante: estes posts são o resultado de uma imersão .NET que estou realizando. Não sou um expert no assunto: caso encontre erros, por favor, me informe nos comentários, eles ajudam muito no nosso aprendizado.

Os elementos essenciais

Na documentação oficial da Microsoft você encontrará uma descrição que diz haver dois elementos essenciais na plataforma .NET:

  • O Common Language Runtime (CLR) – que é o responsável pela execução do nosso código.
  • A biblioteca de classes (Class Library) – que é a API essencial de desenvolvimento.

No link “What is .NET Framework” você vai encontrar a imagem a seguir: você, que usa Java no seu dia a dia, olhe com muita atenção e me diga se não vai te parecer MUITO familiar.

Do Java ao .NET - Parte 2: Arquitetura 2

É tal como no Java. O nome da plataforma Java é péssimo por que confunde a linguagem com a plataforma, mas desde o início (ou quase) nós temos no Java a possibilidade de termos múltiplas linguagens de programação.

Assim como no Java toda linguagem de programação é compatível com a JVM se gerar bytecode que possa ser executado por esta, a MESMA coisa ocorre no .NET. A diferença é que a Microsoft fez um trabalho de comunicação muito melhor: pra começar sua plataforma não tem o mesmo nome que uma de suas linguagens, o que ajuda a entender melhor a diversidade de opções.

Mas há uma diferença: no caso do Java o código vira bytecode que é interpretado pela JVM (e recompilado durante sua execução varias vezes pelo JIT). No caso do .net é gerado código em um formato chamado CIL (Common Intermediate Language), que é compilado pelo CLR para código de máquina e na sequência executado.

(Como nota histórica, já tivemos algo similar no Java: o JRockit, JVM da BEA que foi comprada pela Oracle e na sequência descontinuado, fazia algo nesta direção.
Dica off topic: quer aprender MUITO sobre como a JVM funciona? Leia este livro.)

O Common Language Runtime (CLR) e assemblies

Pense inicialmente no CLR como a “JVM do .net”. É neste componente que estão definidos, por exemplo, quais os tipos que serão usados por todas as linguagens de programação que geram código intermediário (CIL) que será compilado por este.

Falando deste modo você pode pensar que temos essencialmente um compilador de código intermediário para código de máquina apenas, mas o CLR vai além disto. Ele também é responsável por:

  • Controle de segurança de acesso.
  • Definição de tipos essenciais (falei acima).
  • Controle de threads.
  • Compilação de código CIL para código Nativo e execução deste código.
  • Gerência de memória através de um garbage collector, como no caso do Java.
  • Gestão de application domains, ou seja, isolamento de aplicações no .net, tal como processos do sistema operacional.

Sobre o código intermediário – CIL – pense neste como o “bytecode do .net”. Ele é exatamente isto: quando compilamos nosso código C#, F#, VB.net ou qualquer outra linguagem para este formato, geramos binários com a extensão .exe ou .dll.

Estes arquivos são o que no .net chamamos de assemblies. Pense nos assemblies como os “arquivos JAR do Java que você sempre quis ter”. Eu já os vejo como um “quase OSGi“.

(na minha opinião a iniciativa OSGi foi a maior oportunidade perdida da história da plataforma Java: se não conhece, pesquise a respeito)

Isto por que estes arquivos (que são essencialmente arquivos zip, tais como os arquivos JAR – renomeie um e experimente) contém não só o código CIL, mas também importantes metadados:

  • Qual a versão do pacote (assembly).
  • Quais classes poderão ser expostas (você tem no .net a possibilidade de declarar classes (tipos) como privados e visíveis apenas dentro do pacote (olha que coisa linda!)).
  • Conteúdos estáticos como imagens, arquivos, recursos de internacionalização.

Uma das maravilhas do .net é que você pode ter instaladas diferentes versões da plataforma em um mesmo computador e, com isto, ter diferentes programas, implementados para diferentes versões do .net executando em paralelo (side by side execution).

E sabe este negócio de no assembly ter entre os metadados a versão do pacote? No momento em que você compila seu programa informa qual versão da biblioteca quer usar: se houver mais de uma versão instalada da mesma biblioteca no seu computador o CLR seleciona a versão correta para você. Lindo isto, né?

Não entendeu por que é lindo? Coloque então no seu classpath duas versões distintas de um framework como o Hibernate, por exemplo (versão 2 e 5) e me diga o que acontece com seu sistema Java. Na plataforma Java a iniciativa OSGi tinha entre os seus objetivos resolver justamente isto: infelizmente não foi pra frente, e mesmo iniciativas como o Jigsaw não chegam nem perto do que o OSGi prometia e cumpria (uma pena).

Desenvolvedor Java, pense o seguinte: ao invés de termos a JVM interpretando bytecode do JAR, teremos o CLR compilando para código nativo e, na sequência, o executando. Em teoria o tempo de inicialização será bem menor.

Algo que você deve ter em mente: o ambiente .net é para o desenvolvimento de componentes. Cada assembly é um comnponente, isto é, tem bem definido o seu nome, versão e aquilo que deve ser exposto ou não.

(Estou estudando muito a fundo o conceito de assemblies no .net: aguardem um post futuro só sobre este tema)

Links para se aprofundar:

Notas sobre o GAC – Global Assembly Cache

Um componente que você deve prestar muita atenção é o GAC: pense nele como as “shared libs” do seu servidor de aplicações. Há situações nas quais você irá desejar ter um conjunto de assemblies compartilhados entre diferentes programas.

A Microsoft não recomenda que você faça isto, mas acho importante que você saiba de sua existência para o caso de estar lidando com alguma plataforma legada baseada em .net Framework. Mais detalhes sobre o GAC neste link.

Menciono o GAC aqui para que você também faça um paralelo com o carregamento de classes no Java. Enquanto no Java nós normalmente colocamos as bibliotecas que precisamos ou no próprio pacote WAR (ou EAR ou fat JAR), algo similar ocorre no .net.

Os assemblies serão carregados a partir de um diretório ou a partir do GAC.

A Biblioteca de Classes – Class Library

Finalmente temos o segundo componente: a biblioteca de classes. A partir da versão 4.5 do .net Framework temos o conceito de “.net Standard”, que falamos a respeito no primeiro post desta série. Apenas para relembrar: o .net Standard consiste no conjunto de APIs padrão que toda implementação do .net (a partir da versão 4.5 do .net Framework) deve ter implementada para que seja compatível com o padrão .net.

Pense na biblioteca de classes portanto como a API básica que vêm no Java SE. Mas é importante lembrar que além do .net Standard, as diferentes implementações do .net (assunto do primeiro post) podem ter nesta biblioteca de classes APIs específicas para aquela implementação.

É importante estudar a biblioteca de classes para conhecer os padrões de codificação que são adotados no .net. Sendo assim recomendo que você leia este link para que seu código tenha menos cara de Java e mais cara de .net no futuro.

Não há muito o que dizer a respeito deste assunto, mas é importante que você tenha alguns links em mãos:

Código gerenciado e não gerenciado

No Java é possível executar código nativo através do JNI (Java Native Interface), um recurso que pouquíssimas vezes vi ser usado. No caso do .net Framework a situação é diferente: você pode executar código nativo (não gerenciado) de forma direta, e isto é muito interessante.

Mas primeiro é preciso entender a diferença entre código gerenciado (managed) e não gerenciado (unmanaged) no .net.

Código não gerenciado (unmanaged) é aquele que não é executado pelo CLR, ou seja, código que não foi feito focado em .net. Você pode, por exemplo, executar objetos COM ou mesmo código escrito em linguagens como C++. Isto é muito útil quando você quer acesso de baixíssimo nível ou possui requisitos muito bem definidos de consumo de memória ou desempenho.

Já o código gerenciado é aquele escrito focado na plataforma .net, contido em assemblies e executado pelo CLR. A principal vantagem deste tipo de código é o fato deste ser…. gerenciado. Você não precisa se preocupar em liberar memória, gerenciar threads e ainda tem um controle de segurança muito mais sofisticado.

É interessante que código não gerenciado pode iniciar versões do CLR. Por exemplo, o IIS, usado para executar aplicações ASP.net (e ASP.net core) é escrito em código não gerenciado, e inicia o CLR para executar código .net, tal como você pode ver neste link da documentação da Microsoft (Overview of the .NET framework).

Como desenvolvedor Java, faz todo sentido esta facilidade na execução de código nativo em .net: afinal de contas, a Microsoft já tinha toda uma estrutura de desenvolvimento Windows que, naturalmente, cai muito bem ser reaproveitada em código .net.

Aliás, é muito interessante como em .net o acesso ao código nativo se manifesta nas linguagens. No C# por exemplo, temos o conceito de “unsafe code”, que essencialmente é a possibilidade de usarmos ponteiros (como os do C) para apontar para posições específicas de memória. Leia mais a respeito neste link.

Outro ponto em que esta natividade se apresenta é no tipo struct: que funciona de forma muito parecida com o que temos no C/C++ e que é algo que sentia uma falta IMENSA no Java. Estes aspectos low code irão aparecer também nos tipos apresentados pelo CLR: no .net, ao contrário do Java, temos tipos numéricos que são signed e unsigned.

Para aplicações de negócios normalmente ter tipos com sinal ou sem sinal não fazem diferença (por isto não existem no Java), mas quando você precisa interagir com um protocolo de comunicação mais baixo nível (pense no modbus, por exemplo) ter ou não sinal faz uma grande diferença (já senti muito a falta de tipos sem sinal no Java).

(este aspecto baixo nível do .net me surpreendeu muito positivamente)

Links para se aprofundar

Concluindo e prosseguindo

Como deve ter percebido este post é na realidade uma série de dicas para que você conheça melhor o ambiente de execução do código .net para que possamos dar os próximos passos.

É muito importante que você tenha em mente estes paralelos: CIL/Bytecode, CLR/JVM, GAC/Shared libs, especialmente no que diz respeito à execução de código não gerenciado, que é algo incomum a nós, desenvolvedores Java.

Algo que me ajuda muito também é o vocabulário: recomendo que quando leia estes links, preste muita atenção no vocabulário usado pelo pessoal que trabalha com .net. O termo “tipos”, por exemplo, representa classes e por aí vai.

Toquei em apenas alguns pontos neste post: no próximo pretendo falar um pouco sobre os tipos de dados disponibilizados pela CLR, que são um assunto interessantíssimo e que não é intuitivo em diversos aspectos para nós, que trabalhamos com Java. Até lá!

PS: lembrem-se que estes posts são o resultado de uma imersão que estou realizando e que pode conter erros. Por favor, me digam nos comentários caso os encontrem, ok? Valeu!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.