Study Notes

Java

Java é uma Linguagem de Programação multi-paradigma.

Core


Membros Estáticos

O elemento estático (static) é único, pertencente a classe em si, não a instância da classe. Ou seja, todos os objetos daquela classe compartilham a mesma estrutura de dado. É preciso acessar via classe, Ex: Classe.metodoStatic(value).

O principal motivo de usar static é design (constantes, utilitários, loggers, recursos compartilhados). (Na prática, o ganho de performance é bem pequeno).

Exceptions


Uma Exception é um objeto que representa uma anomalia (não necessariamente um erro) durante a execução. Se as exceptions não forem tratadas (caught) o programa é interrompido. Existem dois tipos de exceções: unchecked e checked exceptions. Elas se diferenciam com base no que o compilador obriga o programador a fazer.

Checked

O compilador obriga a tratar com um try-catch ou postergue com throws (até a main postergar também e a JVM receber e interromper o programa). Geralmente, se usa esse tipo de exception quando quer forçar o usuário a tratar a exception e quando tem chance realista de recuperar a operação no tratamento (geralmente operações de I/O). Exemplos:

  • IOException ("Se não foi possível se conectar na rede pode tentar conectar de novo")
  • ClassNotFound
  • SQLException

Unchecked

O compilador NÃO obriga a tratar (embora que, se não tratar, a thread é interrompido de qualquer forma). São as exceptions que herdam de RuntimeException. É bem menos verboso já que não obriga a tratar em toda chamada do método que pode lançar a exception. E, normalmente são erros que não são possível recuperar no tratamento. Por isso (e por se menos verboso) que, o mais comum é criar custom exceptions unchecked que representam alguma violação de regra de negócio ou anomalia (Nem tanto que as frameworks modernas como no Spring utiliza-se mais de unchecked . Exemplo:

  • NullPointerException (durante a execução tentar acessar um elemento null)
  • UnderageUserException (Custom Exception)

java_exceptions.png

Exceptions e Servidores WEB

Uma observação importante é que as Exceptions elas interrompem as threads (uma linha de execução do programa). Se for um programa "simples" que roda em uma thread apenas, checked e unchecked exceptions interrompem a execução dele. No entanto, em ambientes de execução em paralelo/concorrente como em servidores web Tomcat muda. Uma aplicação web é fundamentalmente diferente. Quando inicia-se, o Spring Boot, por exemplo, o código de não é executado diretamente. Sobe-se um servidor web embutido (geralmente o Tomcat), que fica escutando por requisições HTTP em uma porta (ex: 8080). O trabalho do web server é gerenciar um pool de threads/um conjunto de workers. Exemplo do que acontece:

  1. Um client faz uma request para a API (ex: POST /users).
  2. O Tomcat recebe essa requisição e aloca um worker (uma thread) do seu pool e para processar a request.
  3. A thread então executa todo o código. (Controller ⟶ Service ⟶ Repository, etc)
  4. Enquanto isso, o Tomcat continua com suas outras threads escutando por novas requests e atendendo outros clientes simultaneamente. Se uma exception (checked ou unchecked) é lançada durante a execução de uma thread e ela não é tratada, ela vai "subindo" na Stack a partir de onde foi lançada até chegar no Tomcat/Spring. O servidor então captura e "mata" aquela thread. Ou seja, a execução morre mas o servidor (o maestro) continua vivo, rodando. (O spring boot por default imprime no console a stack trace e retorna a response com HTTP 500 Internal Server Error).

Convenções


Nomes de diretórios/packages

Em Java, os diretórios são chamados de packages e a nomenclatura deles deve ser tudo em minúsculo, sem caracteres especiais e palavras sem separadores. Ex: a entidade PlayPackage fica na pasta /playpackage/*. Outro detalhe é que convenção mais comum é usar plural para packages que contêm múltiplas classes do mesmo tipo:

  • exceptions (para múltiplas classes de exceção)
  • controllers (para múltiplos controllers)
  • services (para múltiplos services)
  • repositories (para múltiplos repositories) No entanto, se for um package mais específico/conceitual ou "uma exceção da comunidade", às vezes singular faz mais sentido.
  • config (apesar de ter várias classes de configuração)
  • dto (apesar de ter várias classes de DTOs) Mas ser plural ou não é um pouco mais flexível em termos de convenções

Nomes booleans

As variáveis booleanas usam prefixos como is, has, can, etc... para indicar um estado (true or false). Exemplo: uma variável/função que retorna se é a primeira compra ou não de um usuário. Por mais que a frase seja "it's the first purchase? " em Inglês, o foco é no estado da variável ("é ou não é", "is it or isn't"). Dessa forma o "certo" seria isFirstPurchase. Além disso, é ideal evitar colocar os booleans na negativa, como isNotFirstPurchase porque é menos intuitivo, além de evitar duplas negações.

Arquivos


.jar


Java Archive é literalmente apenas um .zip com uma extensão diferente. (É possível fazer unzip -l arquivo.jar). A finalidade é empacotar vários arquivos em um só para facilitar a distribuição e o deployment. Um .jar tipico possui, por exemplo:

  • *.class: os bytecodes compilados
  • Recursos: .properties, .png, .JSON, .xml, ... que o app precisa.
  • META-INF: são os metadados. Dentro de todo .jar existe esse diretório e dentro dele o arquivo MANIFEST.MF (arquivo chave-valor) que armazena metadados essenciais sobre o arquivo, como a versão que o criou e o ponto de entrada da aplicação.

JAR Executável

Esse são os .jar's que podem ser rodados com java -jar app.jar. A #JVM abre como se fosse um .zip, lê o arquivo META-INF/MANIFEST.MF e procura pelo atributo Main-Class (o ponto de entrada da aplicação). Se o manifesto contiver a linha Main-Class: com.meuprojeto.MinhaClassePrincipal a JVM entende que é para executar a main da classe especificada. Se não houver esse atributo, é printado na CLI a mensagem clássica de no main manifest attribute, in meuapp.jar.

JAR Lib

É um .jar que não é destinado a ser executado sozinho, seu propósito é ser executado em outra aplicação. Exemplos: spring-core.jar, junit.jar, ou qualquer lib que você baixa.

Compilação


Maven


Em vez de ter que baixar manualmente cada dependency (.jar) que o projeto necessita, é possível declarar um único arquivo central as dependências requeridas (pom.xml) .

JVM

Utilitários

Logging


Fazer logging é registrar informações relevantes sobre o comportamento da aplicação em tempo de execução. Exemplo:

  • Mensagens de debug. (ajudam no desenvolvimento)
  • Erros e Exceptions.
  • Informações de processos de negócio. (ex: "Compra criada com sucesso").
  • Métricas e auditoria. (ex: "Usuário X fez login às 10h05").

Em Java já vem embutido na JDK o java.util.logging (JUL), que é uma ferramenta básica de logging que quase ninguém usa por ser limitada e verbosa. Dessa forma, existe outras libs externas que são excelentes, como a SLF4J.

SLF4J

Simple Logging Facade for Java

Spring Ecosystem


Spring não é apenas um framework Java, é um conjunto de frameworks que podem ser combinadas, um ecossistema.

Desenvolvido para a JVM (Java Virtual Machine), o que significa que, embora tenha Java como sua principal linguagem, é oferecido suporte para outras linguagens de programação que também rodam na JVM (como Kotlin e Groovy).

Spring Frameworks

Spring Core


É o módulo que implementa os princípios fundamentais do Spring.

Spring Batch

Spring Security

Spring Data

Spring Boot


[[Distributed System#REST|REST]] API com Spring Boot


DTO


É sempre uma boa prática antes de retornar uma entidade na response mapear-la para uma DTO -Data Transfer Object. Se ao invés de mapear para um DTO retornar a entidade "crua", pode acarretar nos seguintes problemas:

  • Alto Acoplamento: A response da API fica altamente dependente do nome do atributo da entidade. Se renomear um campo quebra a API para o frontend. Usar um DTO desacopla a API da sua persistência.

Service


A camada lógica de código onde ficam escritos as regras de negócio.

No contexto de um serviço que precisa localizar uma entidade, existem básicamente duas formas de implementar.

1. Check-Then-Act

Primeiro faz uma query para saber se a entity existe. Se não encontrar, lançar uma custom exception como ResourceNotFoundException. E, se encontrar, agir (delete/update).

Prós
  • Boa legibilidade. O passo a passo do serviço se torna mais lógico, o que deve fazer.
  • Controle total da error response. Controlar o que retornar de erro é mais fácil e escalável do que ficar tratando com try-catch ou um ponto de erro genérico no @ControllerAdvice.
Contras
  • Potencialmente 2 consultas.
  • Potencial (chance muito baixa) de race condition. Em sistemas de extrema concorrência, é teoricamente possível que outro processo delete a entidade entre o findById / SELECT e a operação DELETE/UPDATE. No entanto, as chances são muito baixas e, mesmo assim, pode-se contornar utilizando o @Transactional (tornando o conjunto de operações no SGBD atômicas).

2. Try-Catch

Executa a operação diretamente e trata a exceção que o JPA/Hibernate pode lançar se a entidade não existir.

Prós
  • Potencialmente 1 consulta.
  • Potencialmente menos código. Se não usar a estrutura try-catch para tratar a exception e deixar genérica pode ter menos código escrito do que a abordagem 1.
Contras
  • Maior acoplamento. Ficar dependendo de exceptions específicas do SGBD driver ou do ORM (como o EmptyResultDataAccessException do Hibernate/JPA) faz o código quebrar se o o driver/ORM mudarem ou atualizarem.
  • Menor flexibilidade. Se precisar do objeto antes de operar (Ex: registrar um log com os dados que foram apagados) com esta abordagem, você não tem o objeto.

Dessa forma, a melhor (disparadamente) abordagem é a 1 (para REST e para aplicações no geral). O argumento mais forte de se preferir a abordagem Try-Catch é que ela faz 1 consulta apenas e a Check-Then-Act faz 2 (ou mais). No entanto, os ORMs (como o Hibernate) possuem cache sofisticado que muitas vezes inibe a segunda query. Além disso, os Database Systems que a maioria das aplicações comunicam são OLTP (otimizados para um alto volume de transações rápidas e curtas).

Controller


A camada que interage com requests e responses exteriores a api.

Criação de Endpoints

Muitas vezes, quando para implementar uma lógica que "mexe" com várias entidades é difícil saber em qual Entity Controller colocar (ou se ainda precisa criar um novo).

Payment Gateway


Payment Gateway é o "portão de caminho" por onde um sistema/SaaS envie/redireciona/comunica a solicitação de pagamento para ser processado.

"Gateway" \equiv algo que controla a passagem de uma lado para outro. (Gateway é traduzido como portão de entrada). Gateway de pagamento controla a passagem de uma compra entre o cliente e a instituição financeira.

Criar um sistema de pagamento próprio, do zero, é uma tarefa difícil e burocrática. Envolve:

  • Gerenciamento de fraude
  • Implementar suporte a múltiplos métodos de pagamento
  • Integrar com múltiplas instituições financeira

E é aqui que entra gateways de pagamento, delegando a responsabilidade, complexidade e burocracia toda para a empresa terceirizada e focando nas regras de negócio interna.

Stripe


Stripe é um dos gateways de pagamento mais famosos/padrão de mercado que existe.

Existem várias formas de integrar a Stripe como gateway de pagamento no sistema. Dentre elas, as abordagens mais comuns são:

Stripe Checkout


1. Usuária clica em comprar

backend cria uma CheckoutSession na Stripe (informando o produto, preço, URLs de sucesso e de falha). e envia a URL da página de checkout da stripe.

2. Redirect para a Stripe Checkout Page

O frontend do Sistema redireciona o usuário para uma página de checkout hospedada pela própria Stripe.

3. Processamento da Compra

O pagamento, validação dos dados, e etc acontece lá.

4. Webhook notification

O backend recebe a confirmação via Webhook se a compra foi ou não bem sucedida. Segue abaixo um exemplo de payload enviado pela stripe por webhook.

{
  "id": "evt_...",
  "object": "event",
  "api_version": "2024-06-20",
  "created": 1725660000,
  "data": {
    "object": {
      // Objeto principal, como a Session, PaymentIntent, etc.
      "id": "cs_test_...",
      "object": "checkout.session",
      "amount_total": 2000,
      "currency": "usd",
      "customer": "cus_...",
      "metadata": {
        // Dados personalizados que você pode ter enviado
      },
      "payment_status": "paid",
      // ... outras propriedades do objeto
    }
  },
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "checkout.session.completed"
}

Payment Intent + Stripe Elements/SDK


  1. backend cria um PaymentIntent (intenção de pagamento) na Stripe (informando valor, moeda, cliente etc.)
  2. No frontend utiliza-se a lib Stripe.js + Elements para montar a página de pagamento “na mão”. Campos de cartão, Pix, boleto etc. são iframes seguros fornecidos pela Stripe (não é lidado com dados sensíveis diretamente).
  3. Stripe processa o pagamento e retorna o resultado direto ao frontend.
  4. Você ainda usa Webhooks no backend para validar e atualizar o status da compra.

Muitos times começam um projeto implementando a integração via Stripe Checkout e depois deixam mais customizado com Stripe Elements/Payment Intent.

Customer


é a abstração de um cliente do seu sistema na Stripe. Esse perfil armazena, por exemplo:

  • Payment Methods. credit card, contas bancárias e etc
  • Dados de Contato. email, nome, etc
  • Histórico de Pagamentos.
  • Subscriptions. assinatura, se aplicável Para cada usuário da aplicação que pretende realizar um pagamento, crie o seu Customer. Pode-se criar durante o cadastro ou quando chegar na checkout page.

Não é preciso criar explicitamente um Customer. Se criar (por ex um CheckoutSession) sem informar um Customer a Stripe gera um implicitamente/ por de baixo dos panos.

Environment Config


O Spring Boot utiliza do src/main/resources/*.{yml,properties} para configurar as variáveis da aplicação. Hoje em dia é preferível utilizar o formato .yml por ser mais legível (hierárquico, sem ficar repetindo os prefixos) e é padrão na industria (como o docker-compose.yml). O Spring Boot automaticamente detecta e carrega arquivos .yml/.yaml, então não precisa configurar nada adicional no código.

Exemplo

  1. [[Java#Exemplo#application.yml|application]]. configurações aplicadas em todos os ambientes
  2. [[Java#Exemplo#application-dev.yml|dev]]. desenvolvimento
  3. [[Java#Exemplo#application-prod.yml|prod]]. produção
application.yml
server:
  port: 8080
  servlet:
    context-path: /api # prefixo geral. (Use /vi/* nos controllers)
    
spring:
  datasource:
    url: jdbc:mysql://localhost/mydb
    username: user
	password: pass
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true # imprime as queries
    properties:
      "[hibernate.format_sql]": true # formata as queries
      
jwt:
  public:
    key: classpath:public.pem # classpath é src/main/resources/public.pem
  private:
    key: classpath:private.pem

stripe:
  public:
    key: ${STRIPE_PUBLIC_KEY:} # Usa env var ou fica vazio
  secret:
    key: ${STRIPE_SECRET_KEY:}
  webhook:
    secret: ${STRIPE_WEBHOOK_SECRET:}
  enabled: true
application-dev.yml
server:
	port: 8080
	
spring:
	datasource:
		url: jdbc:mysql://localhost/mydb
		username: user
		password: pass
	jpa:
		hibernate: 
			ddl-auto: update # ou create-drop para recriar DB a cada restart
		show-sql: true
application-prod.yml
server:
	port: ${PORT:8080}
	
spring:
	datasource:
		url: ${DB_URL}
		username: ${DB_USERNAME}
		password: ${DB_PASSWORD}
	jpa:
		hibernate:
			ddl-auto: update
		show-sql: false

Configuração das var envs

O Spring Boot (SB) é muito flexível na forma como mapeia as variáveis de ambiente para o programa. Ao inicializar uma aplicação, o SB procuro em certos locais para mapear as envs, onde as que estiverem no topo da lista sobrescrevem as que estão embaixo.

  1. Envs do Sistema. No terminal, digitar export STRIPE_PUBLIC_KEY="pk_live_..." para os processos filho ao terminal conseguirem "enxergar" a var. (Ou então, em um container docker passar a flag -e para passar as var envs docker run -e STRIPE_PUBLIC_KEY="pk_live_...".)
  2. CLI args. Passar como args da CLI usando a -- seguido da proprieties . java -jar myapp.jar --stripe.public.key="sua-chave-publica-aqui"
  3. src/main/resources/*.{yml,properties}. (O .properties tem maior precedência que o .yml). Exemplo: Se você configurar um value "estático" para uma key no .properties mas configurar uma var de ambiente ( export ...) o SB iria ignorar a estática do properties. (Ou seja, as variáveis do .yml ou do .properties servem apenas para melhorar a legibilidade).
VS Code

É possível colocar as variáveis de ambiente no arquivo ./vscode/launch.json. esse é um arquivo que descreve como o VS Code deve rodar/debugar sua aplicação .Nele você define coisas como:

  • qual classe ou programa rodar (mainClass)
  • quais argumentos passar (args)
  • quais variáveis de ambiente setar (env)
  • qual diretório usar como cwd Exemplo simples para Java:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Debug App",
      "request": "launch",
      "mainClass": "com.example.Application",
      "projectName": "my-app",
      "env": {
        "SPRING_PROFILES_ACTIVE": "dev",
        "MY_SECRET": "abc123"
      }
    }
  ]
}

On this page