sábado, 24 de agosto de 2024

Kafka - Rodando no WSL 2 (Ubuntu) e usando (Producer e Consumer) a partir do Windows

        Lá estava eu estudando Kafka e me deparei com a seguinte situação: O Kafka instalado no Ubuntu através do WSL 2, rodando legal e eu criando produtores e consumidores a partir do Java usando o Intellij no Windows. Daí o que ocorre é: o Kafka sobe normal, mas o projeto Java não consegue encontrar o serviço, mesmo colocando no projeto o IP do WSL.

      Então descobri que para fazer o projeto Java se comunicar com o Kafka no WSL 2 era preciso configurar a propriedade advertised.listeners no arquivo server.properties no diretório config do Kafka: 


advertised.listeners=PLAINTEXT://ip_do_wsl:9092


        Dessa forma a aplicação mesmo em outra rede consegue encontrar o Kafka e criar e ler os tópicos, produzindo e consumindo suas menagens. A propriedade advertised.listeners no Apache Kafka desempenha um papel crucial na comunicação entre os clientes (produtores e consumidores) e os brokers do Kafka, especialmente em cenários de redes distribuídas ou ambientes com várias interfaces de rede.

        Ela especifica os endereços IP ou nomes de host e portas que os brokers Kafka devem anunciar para os clientes (produtores, consumidores, etc.). Esses são os endereços que os clientes usarão para se conectar ao broker após receberem a metadata inicial do Kafka.

        Uma questão é: o IP do WSL pode mudar toda vez que for iniciado. Ficar mudando isso tanto no arquivo de configuração do Kafka quanto na aplicação (ou aplicações como é o caso microsseviços) é chato. Então pra mudar na aplicação (no meu caso Java) muda muito de caso pra caso, mas a ideia inicial seria usar variável de ambiente ou arquivo de propriedades tirando do código a dependência. 

     Para o Kafka recorri ao GPT que gerou o script bash a seguir que troca o IP no arquivo config/server.properties:


#!/bin/bash

# Passo 1: Obter o IP do WSL 2

WSL_IP=$(hostname -I | awk '{print $1}')


# Passo 2: Caminho para o arquivo server.properties do Kafka

KAFKA_CONFIG_PATH="/caminho/para/seu/kafka/config/server.properties"


# Passo 3: Atualizar o advertised.listeners com o IP atual do WSL 2

# Verifique se o arquivo existe

if [ -f "$KAFKA_CONFIG_PATH" ]; then

    # Usar sed para substituir o advertised.listeners existente ou adicionar se não existir

    if grep -q "^advertised.listeners=" "$KAFKA_CONFIG_PATH"; then

        # Substitui a linha existente

        sed -i "s/^advertised.listeners=.*/advertised.listeners=PLAINTEXT:\/\/$WSL_IP:9092/" "$KAFKA_CONFIG_PATH"

    else

        # Adiciona a linha se não existir

        echo "advertised.listeners=PLAINTEXT://$WSL_IP:9092" >> "$KAFKA_CONFIG_PATH"

    fi

    echo "advertised.listeners atualizado com o IP: $WSL_IP"

else

    echo "Erro: Arquivo server.properties não encontrado em $KAFKA_CONFIG_PATH"

    exit 1

fi


# Passo 4: Iniciar ou reiniciar o Kafka para aplicar as mudanças

# Ajuste o comando abaixo para iniciar ou reiniciar seu Kafka

# Exemplo para iniciar Kafka:

# /caminho/para/seu/kafka/bin/kafka-server-start.sh -daemon $KAFKA_CONFIG_PATH

echo "Script concluído. Certifique-se de reiniciar o Kafka para aplicar as mudanças."


       Basta salvar o script como atualiza_kafka.sh na pasta do Kafka, dar permissão de execução com chmod +x atualiza_kafka.sh e executar antes de subir o Kafka.

        Ainda não executei o Kafka pelo Docker subindo também os produtores e consumidores em uma mesma rede com Docker Compose então não sei se muda muita coisa, mas como seria a mesma rede, acredito que essa configuração de advertised.listeners não seja necessária, de qualquer forma esse artigo pode ser útil pra mim mesmo no futuro e outros que estão aprendendo a ferramenta.







domingo, 4 de agosto de 2024

Metodologia Twelve-Factor

         A metodologia Twelve-Factor App é um conjunto de 12 regras para manter uma aplicação de forma eficaz, sendo elas:


  1. Codebase (Código-fonte único): Uma aplicação deve ser armazenada em um único repositório de código, com múltiplas implantações derivadas do mesmo código-base.

  2. Dependencies (Dependências explícitas): Todas as dependências da aplicação, incluindo bibliotecas e ferramentas de sistema, devem ser declaradas explicitamente e gerenciadas de forma isolada.

  3. Config (Configurações): As configurações da aplicação devem ser armazenadas em variáveis de ambiente, não no código-fonte, para permitir a configuração flexível em diferentes ambientes.

  4. Backing Services (Serviços de Back-end): Os serviços de back-end, como bancos de dados e filas, devem ser tratados como recursos externos, acessíveis por meio de interfaces padrão.

  5. Build, Release, Run (Construir, entregar, executar): O processo de build, entrega e execução da aplicação deve ser separado em etapas distintas, com cada etapa tendo suas próprias responsabilidades e garantindo a consistência entre ambientes.

  6. Processes (Processos): As aplicações devem ser executadas como processos independentes, leves e sem estado (Stateless), para facilitar a escalabilidade e a resiliência.

  7. Port Binding (Ligação de porta): As aplicações devem ser autocontidas e expor serviços por meio de portas, para que possam ser facilmente conectados a outras aplicações e serviços.

  8. Concurrency (Concorrência): As aplicações devem escalar horizontalmente, adicionando instâncias concorrentes para lidar com cargas de trabalho aumentadas.

  9. Disposability (Descartabilidade): As aplicações devem ser fáceis de iniciar e parar rapidamente, sem impacto para outras partes do sistema, para facilitar o deploy e a atualização contínua.

  10. Dev/Prod Parity (Paridade dev/prod): Os ambientes de desenvolvimento, testes e produção devem ser o mais semelhantes possível, para minimizar diferenças e evitar problemas de compatibilidade.

  11. Logs (Registros): As aplicações devem produzir logs estruturados e acessíveis por meio de interfaces padronizadas, para facilitar a depuração e o monitoramento.

  12. Admin Processes (Processos de administração): As tarefas administrativas, como migrações de banco de dados e limpeza de caches, devem ser executadas como processos únicos e rastreáveis.

Balanceamento com NGINX

         Além de expor conteúdo estático e proxy reverso, os servidores web também fazem balanceamento entre nós da aplicação. Nesse artigo vou mostrar o NGINX fazendo o balanceamento entre 2 nós.

         Para fazer o balanceamento, no arquivo nginx.conf que fica dentro de /etc/nginx/conf.d declaramos os servidores (com ip e porta) na seção upstream e depois usamos no proxy pass. Cada upstream deve ter um nome para poder ser referenciado no server, nesse caso o nome foi meu_upstream:


upstream meu_upstream{

        # ip_hash habilita sticky sessions com base no endereço IP do cliente, para garantir a seção        

        ip_hash;

server ip_server_01:8080;

server ip_server_02:8080;

}


server {

    listen 80;

    

    location / {

        proxy_pass http://meu_upstream;

        proxy_set_header Host $host;

        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;

    }

}


        Pronto, dessa forma temos um balanceamento configurado.

Redis com Spring Boot

         O Redis é um banco de dados em memória para criar caches na aplicação, tornando o acesso a dado mais rápidos.

      Claro que não é simplesmente fazer cache de toda a aplicação, por isso é preciso entender como funciona o sistema. O cache é indicado para tabelas que tem bastante acesso e pouca alteração, diminuindo dessa forma o acesso desnecessário ao banco e ajudando na saúde da aplicação como um todo, pois desonerando o acesso ao banco com coisas que mudam pouco deixamos ele livre para responder a requisições mais importantes.

        Pra começarmos, precisamos adicionar a dependência no projeto:


<dependency>

<groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-cache</artifactId>

</dependency>


    Uma vez adicionada a dependência, é preciso adicionar o endereço e a porta no arquivo application.properties:


spring.data.redis.host=127.0.0.1

spring.data.redis.port=6379


        É preciso também habilitar o Cache na aplicação, assim como é habilitado as requisições assíncronas, com o uso de anotação na classe principal:


@EnableCaching


        Agora o Redis está configurado para ser usado, o que falta é usar o Cache no projeto. Os métodos que desejamos que tenham seu resultado guardado no cache (geralmente estão nas classes de negócio - também chamadas de services - ou no DAO). Basta anotá-los com @Cacheable e dar um nome para esses dados na tabela de cache:


@Cacheable(value = "nome_da_tb_de_cache")


        E para atualizar os dados, fazendo o sistema ir buscar no banco? Podemos anotar os métodos de inserção, update e exclusão com @CacheEvict passando o nome da tabela de cache e se queremos limpar todos os registros:


@CacheEvict(value = "nome_da_tb_de_cache", allEntries = true)


     Isso vai limpar os dados. Também podemos definir um tempo de vida (TTL) para o cache no application.properties, o que dá a ele um tempo padrão de vida (o default é ficar pra sempre).


# TTL em milissegundos (10 minutos)

spring.cache.redis.time-to-live=600000  


        Existem outras formas que podem ser pesquisadas caso haja necessidade, como o tamanho do cache.









terça-feira, 30 de julho de 2024

Pool de conexões no Spring Boot

         Toda aplicação precisa de um pool de conexões para gerenciar o acesso ao banco de dados de forma eficiente. No Spring Boot é carregado um pool automaticamente, mas devemos configurar para o ambiente de produção de acordo com a necessidade.

        

        A configuração varia de acordo com cada aplicação, então é preciso testar e observar o comportamento da aplicação pra saber se mais conexões disponíveis são necessárias.


        Segue um exemplo de configurações de pool no Spring Boot:


spring.datasource.hikari.minimum-idle=25

spring.datasource.hikari.maximum-pool-size=50

spring.datasource.hikari.connectionTimeout=15000  

spring.datasource.hikari.idleTimeout=600000

spring.datasource.hikari.maxLifetime=1800000 



HTTPS no NGINX

        Para habilitar o HTTPS em um servidor, é necessário adquirir um certificado digital SSL/TLS de uma Autoridade Certificadora (CA) confiável. É possível obter um certificado digital de maneira gratuita, utilizando o serviço Let's Encrypt.

        Para usar o HTTPS no Nginx e configurar o certificado digital é preciso copiar o certificado e a cheve para dentro do servidor nos caminhos mostrados abaixo:

        Certificado ssl:                     /etc/nginx/certificates/certificado.crt;

        Chave ssl do certificado:     /etc/nginx/certificates/chave.key;

        E então mudar o arquivo nginx.conf de: 

server {
    listen 80;
    server_name meu_dominio.com.br;
    
    location / {
        proxy_pass http://ip_servidor:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

        Para:

server {
    listen 80;
    server_name meu_dominio.com.br;
location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name meu_dominio.com.br;
ssl_certificate /etc/nginx/certificates/certificado.crt; ssl_certificate_key /etc/nginx/certificates/chave.key; location / { proxy_pass http://ip_servidor:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

        

GitHub Actions

    Nesse artigo rápido eu mostro um exemplo do uso do GitHub Actions para um projeto Java. O arquivo deve ficar em uma pasta workflow dentro da pasta .github do projeto (.github/workflows).


name: CI/CD with Docker and GitHub Actions

on:

  push:

    branches:

      - main

jobs:

  build:

    runs-on: ubuntu-latest

    steps:

    - name: Checkout repository

      uses: actions/checkout@v2

    - name: Build Docker image

      run: docker build -t gbdaniel/projeto:latest .

    - name: Log into Docker Hub

      run: docker login -u gbdaniel -p ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Push image to Docker Hub

      run: docker push gbdaniel/projeto:latest

  deploy:

    runs-on: ubuntu-latest

    needs: build

    steps:

    - name: Install SSH key

      uses: webfactory/ssh-agent@v0.5.3

      with:

        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

    - name: SSH into EC2 instance and update Docker Compose

      run: |

        ssh -o StrictHostKeyChecking=no ec2-user@IP_DO_SERVIDOR_EC2 "cd /home/ec2-user && docker-compose pull && docker-compose up -d"


segunda-feira, 1 de abril de 2024

Diferença entre Forward e Redirect no retorno do Controller do SpringMVC

        Nesse artigo vou mostrar a diferença entre o Forward e o Redirect em uma chamada para um Controller do SpringMVC que retorna a String com o endereço a ser chamado depois da ação. Além de retornar a String com a página a ser carregada você pode especificar como esse retorno vai ser feito usando as opções Forward e o Redirect concatenadas ao endereço: 

Forward:

- O forward é um encaminhamento interno, onde a mesma requisição é reenviada para outro controller ou endpoint no servidor.
- O forward é executado do lado do servidor, sem envolver uma nova requisição do cliente.
- O URL no navegador permanece o mesmo.
- Os dados da requisição original são preservados e podem ser acessados pelo controller ou endpoint de destino.
- É útil quando você deseja processar dados adicionais ou realizar ações no backend antes de exibir a próxima página.

Redirect:
- O redirect é uma resposta do servidor ao cliente para redirecioná-lo para um novo URL.
- O redirect envolve uma nova requisição do cliente para o novo URL.
- O URL no navegador é atualizado para o novo URL.
- Os dados da requisição original não são preservados. Eles devem ser passados através de parâmetros de URL, atributos de sessão ou armazenados em algum outro local persistente.
- É útil quando você deseja redirecionar o cliente para uma nova página, como após um cadastro bem-sucedido ou para evitar reenvio de formulários quando atualizar a página.

        No contexto específico onde você quer permanecer na mesma página de cadastro com os dados pré-preenchidos em caso de erro, o uso de forward é mais adequado. Ao retornar a String que representa a mesma página de cadastro, você está realizando um forward interno, mantendo os dados da requisição original e exibindo a mesma página com os dados pré-preenchidos.

quinta-feira, 8 de fevereiro de 2024

Relacionamento bidirecional recursivo com JPA

           Neste artigo vou mostrar um exemplo de um relacionamento bidirecional recursivo, ou seja, o objeto faz referência a ele mesmo. 


As vezes ao invés de usar herança, essa solução resolve de forma mais simples não tendo que alterar muita coisa no modelo já existente. Recentemente usei ela para o desdobramento de uma entidade que era a base do sistema e foi reclassificado em elementos "pai e filho". 


Claro que houve a necessidade de aumentar alguns campos na tabela que serão usados apenas quando for "filho" ou "pai", mas a desnormalização do modelo muitas vezes se faz necessário. O uso de outra solução acarretaria em uma grande mudança em todo o sistema. 


Para o exemplo vamos pensar para fins didáticos em uma cadeia de comando onde um Chefe tem Subordinados, mas também está subordinado à um Chefe.

 

No banco existem os seguintes campos: id, nome e chefe_superior_id. Segue o código:

 

 

import javax.persistence.*;

import java.util.List;

 

@Entity

@Table(name = "chefe")

public class Chefe {

 

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

 

    @Column(name = "nome")

    private String nome;

 

  @OneToMany(mappedBy = "chefe", cascade = CascadeType.ALL, fetch = FetchType.LAZY)

    private List<Chefe> subordinados;

 

    @ManyToOne

    @JoinColumn(name = "chefe_superior_id")

    private Chefe chefe;

 

    // getters e setters

 

}

segunda-feira, 18 de dezembro de 2023

Aumentando o limite do tamanho de upload de arquivos do JBoss EAP

 

Nesse artigo vou mostrar como permitir requisições de grande tamanho para o JBoss EAP. Por padrão ele barra requisições maiores que 10Mb, o que para os dias atuais não é muita coisa, já que qualquer imagem ou PDF facilmente passa esse tamanho.

 

No JBoss EAP, a partir da versão 7 foi incluído um novo servidor web extremamente leve e de alto desempenho da própria RedHat chamado Undertown, substituindo o Tomcat que rodava internamente no JBoss. No standalone.xml encontramos seu submódulo como abaixo:

 

<subsystem xmlns="urn:jboss:domain:undertow:4.0">

<buffer-cache name="default"/>

     <server name="default-server">

<http-listener name="default" socket-binding="http" enable-http2="false"/>

<ajp-listener name="ajp" socket-binding="ajp" />

<host name="default-host" alias="localhost">

               <location name="/" handler="welcome-content"/>

               <filter-ref name="server-header"/>

               <filter-ref name="x-powered-by-header"/>

</host>

</server>

     <servlet-container name="default">

          <jsp-config/>

          <websockets/>

     </servlet-container>

     <handlers>

<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>

</handlers>

<filters>

<response-header name="server-header" header-name="Server" header-value="JBoss-EAP/7"/>

          <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>

    </filters>

</subsystem>

 

 

E é nele que precisamos configurar a permissão para grandes arquivos, dentro das tags <http-listener> e <ajp-listener>. Nesse exemplo vou configurar para 1GB, mas você precisa ver de acordo com sua necessidade.

 

<subsystem xmlns="urn:jboss:domain:undertow:4.0">

<buffer-cache name="default"/>

     <server name="default-server">

<http-listener name="default" socket-binding="http" max-post-size="1073741824" enable-http2="false"/>

<ajp-listener name="ajp" socket-binding="ajp" max-post-size="1073741824" />

<host name="default-host" alias="localhost">

               <location name="/" handler="welcome-content"/>

               <filter-ref name="server-header"/>

               <filter-ref name="x-powered-by-header"/>

</host>

</server>

     <servlet-container name="default">

          <jsp-config/>

          <websockets/>

     </servlet-container>

     <handlers>

<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>

</handlers>

<filters>

<response-header name="server-header" header-name="Server" header-value="JBoss-EAP/7"/>

          <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>

    </filters>

</subsystem>

 

 

Com isso o JBoss agora passa a permitir um grande tráfego de dados, mas ainda é possível configurar outros controles como no Spring por exemplo.