Anotações curso ‘Arquitetura e design de projetos em Java’ da caelum

8 minutos de leitura

Atualizado em:

Em 2018 fiz um curso na Caelum intitulado 'Arquitetura e design de projetos em Java', acho que este curso nem esta mais disponível, pelo menos não com este nome, mas me rendeu ótimos conhecimentos. Este post contém apenas notas simples destas aulas, espero que seja útil para alguém.

Plataforma Java

Características do Java

  • Simples
  • Orientado a Objetos
  • Pacotes nativos para usar a rede
  • Robusto (Se cair não impacta o SO)
  • Interpretado (Java -> bytecode <- JVM)
  • Seguro
  • Arquitetura neutra, ao compilar não precisa informar para qual arquitetura de processador
  • Portabilidade, roda em qualquer SO que tem a JVM
  • Performático, principalmente pelo JIT (Just in time) AKA hotspot
  • Multthread, tem suporte nativo
  • O java é opensource, porém há alguns recursos fechados pela Oracle, o JIT e o GC são um deles

JIT (just in time)

Recompila o código para otimizar ao longo do tempo.

Aumentar a responsabilidade melhora a performance, pois o JIT precisa rodar muitas vezes um método para otimizá-lo, com a responsabilidade alta, há muito reaproveitamento.

Os parâmetros -server ou -client devem ser passados para a JRE para melhorar o desempenho, em uma aplicação servidora é esperado que esta seja mais pesada, com o parâmetro -server o JIT demora mais para otimizar um método (por volta de 3000 vezes), já que não é esperado um retorno de desempenho alto logo no inicio do carregamento da aplicação. Diferente de uma aplicação do lado do cliente, que é desejável que o desempenho seja o mais rápido possível.

O JIT recomeça toda vez que uma aplicação é iniciada, por isso há técnicas para 'esquentar' a execução, com rotinas que forcem a execução de métodos para usados para que o JIT já o otimize antes do usuário precisar.

Por causa do JIT pode considerar que o Java é interpretado e compilada.

Um das otimizações é o inline, que retira o conteúdo do método e coloca diretamente no corpo substituindo a chamada do método.

GC (garbage collector)

Quando o GC passa ocorre o 'stop de world', onde a aplicação para por uns instantes, como 95% dos objetos tem vida curta (hipótese das gerações) rapidamente seriam coletadas pelo GC, foi definida a divisão da memória heap entre young generational e old generational, deste modo, não é executado o 'full gc' constantemente.

O GC passa constantemente na young e promove os objetos para a old, onde passa com menor frequência. Esse processo e chamado de 'generational copying'. Quando promovido, o objeto também é compactado.

A heap mantém apenas objetos, as classes, classes anônimas, variáveis estáticas e o pool de Strings são mantidas na 'perm gem' (até o java7) ou metaspace (java8), fora da heap. As lambdas, mesmo sendo quase uma classe anônima, vai para a heap.

Quando o metaspace enche, a JVM aloca espaço em disco.

Geralmente é uma boa pratica deixar a JVM cuidar do GC, usar o System.gc() ou até o método finalize(), dificilmente vai ser mais eficiente que delegar essa responsabilidade.

Classloaders

Classloaders é o objeto responsável pelo carregamento de classes Java para a memória a partir de um array de bytes que contém seus bytecodes.

Bootstrap (carrega o rt.jar) -> ExtensionCL -> AppCL (classpath)

Quando uma aplicação tenta carregar uma classe, pede ao AppCL, ela antes de carregar verifica em sua cache se já foi carregada, se não, tenta carregar no extension, e este tenta ao bootstrap, ai desce a responsabilidade até alguém carregar.

No tomcat por ex, ele cria um WebappCL para cada aplicação carregada, mas essa estende de bootstrap para garantir que não haja conflitos entre libs da aplicação.

Toda classe é identificada pelo fully qualified name (pacote+nome) e o CL que a carregou, por isso a mesma classe, carregada de CL diferentes não são iguais.

A URLCL quando passa null no 2 parâmetro indica que o pai é o bootstrap, se herdasse de AppCL, quanto tento carregar uma classe por ele, o AppCL responderia com a classe da aplicação (ex 3.4)

Classloaders pode quebrar o Singleton, pois poderia carregar duas instâncias desse objeto com CL diferentes

A JVM é um poderoso executor de bytecode, não interessa de onde eles vêm, independente de onde estiverem rodando. É por esse motivo que muitos afirmam que a linguagem Java não é o componente mais importante da plataforma, mas, sim, a JVM e o bytecode

Tópicos de Orientação a Objetos

Objetos imutáveis

Classes final protege a classe pois ela vira imutável, assim ninguém pode especializar a classe e sobrescrever algum comportamento, atributos da classe devem se final, e objetos passados no construtor devem ser clonados para garantir a imutabilidade destes objetos, assim como os geters devem ser clonados.

Modelo anêmico

São classes de negocio sem comportamento, só com os getters e setters.

Herança

Se faz herança por causa do reuso (Composição [tem um]) ou Polimorfismo (é um).

Quebra de encapsulamento ocorre quando, ao não conhecer a implementação da classe pai, inserimos um comportamento inconsistente.

Prós    

  • Reuso
  • Polimorfismo

Quando não tem esses dois motivos para usar a herança pode ser um problema para a aplicação, talvez valha a pena usar composição

Contras

  • Acoplamento
    • Semântico: Quando há uma alteração na classe mãe que não ocorre erro de compilação, como a alteração do comportamento de um método que afeta o resultado em tempo de execução do código, testes resolvem isso
    • Sintático: quando há uma alteração na classe mãe que dá um erro de compilação como a alteração do nome de um método que é sobrescrito ou utilizado nas classes filhas

Analise das classes 'Útil'

  • Baixa coesão
  • Métodos estáticos se mentem no metaspace para sempre
  • Não aderente a OO
  • Não adere a SRP (Single Responsibility Principle)

SOLID

  • SRP (Single Responsibility Principle)
    • Cada classe tem uma responsabilidade única
  • OCP (Open closed Principle)
    • Sem alterar o código alterar o comportamento da classe, por interface pode-se criar novas especializações sem alterar o código dele, composição
  • LSP (Liskov Subistitution Principle)
    • Não consegue substituir sua classe pela classe mãe, não consegue fazer polimorfismo
  • ISP (Interface segregation Principle)
    • Diz que não deve ter herança de algo se você não vai usar, recomenda composição
  • DIP (principio da inversão de dependências)
    • Em vez de depender de uma classe abstrata, depender de uma interface

Decisões arquiteturais

Requisitos não funcionais ou *bilities

  • Escalabilidade
    • Loadbalance para adicionar mais instancias, etc
    • Tipos:
      • Vertical: Hardware
      • Horizontal: Mais maquinas
  • Disponibilidade
    • Loadbalance para adicionar redundância, etc
  • Desempenho
    • Cache, no sql, ux, etc
  • Confiabilidade
    • Integridade com hashcode, etc
    • Testes unitario, carga, stress, etc
  • Extensibilidade
    • Baixo acoplamento, alta coesão
  • Manutenção
    • Monitoramento
    • ELK (Elasticsearch, logstash, kibana), etc
  • Gerenciabilidade
    • AWS, etc
  • Segurança
    • encriptação do canal de comunicação, auditoria, etc

Arquiteturas

  • Standalone
    • 1 nó
  • Cliente servidor
    • Desktop - > Persistência
  • 3-tier (tier: camada física)
    • Navegador -> servidor -> persistência
  • n-tier
    • Navegador -> loadbalance -> servidor 1 ... servidor n -> persistência
  • Microserviços
    • Aplicações pequenas (em contexto) reutilizáveis
    • Vantagens:
      • Resiliência
      • Flexibilidade

Testes

Testes

TDD (Test Driven Development) - Criar os testes antes de desenvolver, assim você tem feedbacks curtos e constantes.

“Quanto mais tempo se passa maior o custo para corrigir um bug.”

Prós

  • Serve de documentação para conhecer o sistema
  • Segurança para o programador de fazer alterações no código sem medo de quebrar o sistema
  • Força a manter o baixo acoplamento

Contras

  • Custo
  • Difícil de criar e manter

Código legado não é um código antigo, mas sim um código sem testes

Mock

Substitui um acoplamento complexo por esse objeto falso, por exemplo um DAO, o mock poder retornar um objeto ao invés de ir no banco

Tipos de testes

  • Teste unitário - Teste a menor unidade de código (métodos)
  • Teste de integração - Componentes, testa a integração entre 2 ou mais componentes, por exemplo a conexão com o BD
  • Teste de aceitação - Toda a aplicação, com as telas

Conceito de integração continua

O código deve ser constantemente testado, o jenkins por exemplo, executa os testes automaticamente

Conceito de deploy continuo

Sempre que fizer um commit é gerado uma versão para produção, o usuário tem atualizações mais rápido. Diminui custo de feedback.

[]`s

Deixe um comentário