quinta-feira, 18 de dezembro de 2025

API first - ótimo com IA

        API first é uma abordagem de desenvolvimento de software onde o contrato de definição da API é feito primeiro que o código.

        Aplicar o "API first" sem IA é um trabalho chato e acho que demora mais do que sentar na reunião e fazer algumas anotações sobre os recursos e suas interações na mão, depois codar a API e disponibilizar para a outra equipe (se for o caso) e fazer alguma correção. Achei realmente muito cansativo, trabalhoso e demorado. Em uma empresa que trabalhei, para APIs externas era usado esse conceito, mas não era o time de dev do projeto (vulgo squad) que fazia, era uma equipe só pra isso, tinha que abrir chamado e passar o JSON do recurso, os endpoints e tal (meio como vou fazer abaixo com o prompt da IA).

        O motivo principal de fazerem isso na empresa para APIs externas ao invés de simplesmente configurar o Swagger no projeto com Spring Boot e ele ir tomando forma acredito era pra disponibilizar o Swagger para clientes externos, uma vez que o Swagger do projeto não ficaria exposto em produção, então eles tinha um endereço de documentação onde expunham o .yaml gerado para clientes externos acessarem. Pra equipes internas não sei se valeria o trabalho. 

        Com a IA é molesa, mas na mão, vi em um vídeo (que vou deixar no final) e achei um inferno! Então vou deixar o prompt aqui de exemplo para usar sempre que for preciso. Claro que no momento de gerar pode (e deve) passar mais informações, como campos obrigatórios, se usa autenticação, qual tipo de autenticação, url de autenticação, tudo isso é importante para a documentação da API!

Cria pra mim o .yaml do swagger para API first seguindo a seguinte especificação:

openapi: 3.1.0

title: pedidos
description: DOC API pedidos

servers: https://localhost:8080/pedidos-api

versione no padrão: /api/v1/recurso

Recurso /pedidos

GET - 200 OK

GET {id} - 200 OK ou 404 Not Found

POST - 201 Created ou 400 Bad Request

PUT {id} - 202 Accepted ou 404 Not Found

DELETE {id} - 202 Accepted ou 404 Not Found

JSON objeto:
 
{
  "idPedido": 160253,
  "idCliente": 123658,
  "produtos": [{
        "idProduto": 6548,
        "descricao": "Livro Java - Como programar",
        "quantidade": 1,
        "valorUnitario": 250.00
  }]
  
}

O link do vídeo que ensina a fazer a documentação na mão é o: https://www.youtube.com/watch?v=cksLbT8nDB4

segunda-feira, 8 de dezembro de 2025

JPA - Anotações de relacionamento e dicas úteis

        Nesse artigo vou abrodar as anotações de relacionamento do JPA trazendo algumas dicas úteis, principalmente no @ManyToMany:


 1. @OneToOne


O que é: Relacionamento um-para-um → um registro se relaciona com exatamente um outro registro, não podendo pertencer a mais de um.


- Pode ser unidirecional ou bidirecional

- Bidirecional exige 'mappedBy'.


Exemplo (unidirecional) Usuário ↔ Endereço (apenas se for 1 único endereço por pessoa)


@Entity

class Pessoa {

    @Id

    private Long id;


    @OneToOne

    private Endereco endereco;

}


Exemplo (bidirecional)


@Entity

class Pessoa {

    @Id

    private Long id;


    @OneToOne(mappedBy = "pessoa")

    private Endereco endereco;

}


@Entity

class Endereco {

    @Id

    private Long id;


    @OneToOne

    @JoinColumn(name = "pessoa_id")

    private Pessoa pessoa;

}


2. @OneToMany


O que é: Um objeto possui muitos do outro lado.

Ex.: Um cliente tem muitos pedidos.


- É bidirecional, com o lado dono sendo o '@ManyToOne'.


Exemplo (bidirecional)


@Entity

class Cliente {

    @Id

    private Long id;


    @OneToMany(mappedBy = "cliente")

    private List<Pedido> pedidos;

}


@Entity

class Pedido {

    @Id

    private Long id;


    @ManyToOne

    @JoinColumn(name = "cliente_id")

    private Cliente cliente;

}


3. @ManyToOne


O que é: Muitos objetos se relacionam com um do outro lado.


- Pode ser unidirecional, mas geralmente faz parte de um relacionamento bidirecional com '@OneToMany'.


Exemplo


@Entity

class Pedido {

    @Id

    private Long id;


    @ManyToOne

    @JoinColumn(name = "cliente_id")

    private Cliente cliente;

}


4. @ManyToMany


O que é: Muitos para muitos.

Ex.: alunos x cursos.


- Pode ser unidirecional ou bidirecional.

- Normalmente bidirecional, e JPA cria tabela intermediária.

- Pode haver casos de precisar manusear o objeto intermediário, então não use ManyToMany, modele as relações como: Na tabela intermediária -> @ManyToOne para cada lado; Nos lados -> @OneToMany para a entidade intermediária (dependendo da navegação). Vou mostrar um exemplo mais abaixo. Isso transforma o N:N em duas relações 1:N + N:1.


Exemplo @ManyToMany (bidirecional)


@Entity

class Aluno {

    @Id

    private Long id;


    @ManyToMany(mappedBy = "alunos")

    private List<Curso> cursos;

}


@Entity

class Curso {

    @Id

    private Long id;


    @ManyToMany

    @JoinTable(

        name = "aluno_curso",

        joinColumns = @JoinColumn(name = "curso_id"),

        inverseJoinColumns = @JoinColumn(name = "aluno_id")

    )

    private List<Aluno> alunos;

}


        Agora vamos ao exemplo onde precisamos trabalhar com o objeto intermediário, no caso, a tabela aluno_curso.

        Precisamos adicionar campos a ela, colocar notas dos alunos, data da matricula, etc.Passamos a ter a Entidade 

        AlunoCurso e agora tando Aluno quanto Curso apontam pra ela com @OneToMany e ela aponta pra ambos com @ManyToOne:


@Entity

public class Aluno {

    @Id

    private Long id;


    private String nome;


    @OneToMany(mappedBy = "aluno", cascade = CascadeType.ALL)

    private List<AlunoCurso> cursos = new ArrayList<>();

}


@Entity

public class Curso {


    @Id

    private Long id;


    private String nome;


    @OneToMany(mappedBy = "curso", cascade = CascadeType.ALL)

    private List<AlunoCurso> alunos = new ArrayList<>();

}


@Entity

public class AlunoCurso {


    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;


    @ManyToOne

    @JoinColumn(name = "aluno_id")

    private Aluno aluno;


    @ManyToOne

    @JoinColumn(name = "curso_id")

    private Curso curso;


    private LocalDate dataMatricula;

    private List<Double> notas;

}


Dicas importantes:

- Só faça carregamento bidirecionais se for realmente preciso.

- Sempre prefira LAZY nos @ManyToOne, evita carregar todo o curso ao carregar um aluno.

- Evite usar chave composta quando possível, ID próprio simplifica a vida.

- Os lados “Aluno” e “Curso” podem ser unidirecionais, você não é obrigado a colocar o @OneToMany neles se não precisar navegar, quem sabe usar um @Transient e carregar somente quando necessário. Por exemplo, você pode deixar o lado do Curso unidirecional, removendo o @OneToMany e colocando o @Transient apenas para uso em memória. Importante: O Curso continua relacionado à tabela intermediária (AlunoCurso) — você só está escolhendo não mapear a navegação inversa no JPA. Isso melhora performance, reduz carga de memória e evita N+1.


@Entity

public class Curso {

    @Id

    private Long id;


    private String nome;


    @Transient

    private List<AlunoCurso> alunosCurso; // carregado manualmente quando e onde precisar

}


        Se quiser pode remover completamente ou mapear apenas alunos diretamente (de forma transiente) carregando manualmente também quando precisar. Esses detalhes variam de acordo com o negócio.





terça-feira, 11 de novembro de 2025

Configurando CSRF (Cross-Site Request Forgery) - Spring Security

        Nesse post vou falar sobre CSRF (Cross-Site Request Forgery - Falsificação de solicitação entre sites). O CSRF é um tipo de ataque onde um site malicioso tenta forçar o navegador de um usuário autenticado a enviar uma requisição indesejada para outro site (por exemplo, seu sistema), usando os cookies de sessão válidos daquele usuário.

        O Spring Security, por padrão, habilita a proteção contra CSRF para evitar isso — especialmente em aplicações web com sessões e formulários HTML (POST). Podemos desativar caso iremos usar aplicações com tokens JWT ou usar a configuração padrão. Abaixo temos os 2 exemplos para o Spring Boot a partir da versão 3.0: 


1. Desativando o CSRF (uso em APIs REST, JWT, stateless)

Esse é o que você mostrou:

httpSecurity
    .csrf(csrf -> csrf.disable())
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/**").permitAll()
        .anyRequest().authenticated()
    );
  • A aplicação não mantém sessão (SessionCreationPolicy.STATELESS costuma acompanhar isso).

  • Ideal para APIs REST com tokens JWT, pois o ataque CSRF depende de cookies, e aqui normalmente não há cookies.

Exemplo completo:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/auth/**").permitAll()
            .anyRequest().authenticated()
        )
        .httpBasic(withDefaults());
    return http.build();
}


2. Mantendo o CSRF Ativado (uso em apps web com formulários HTML)

        Aqui o CSRF é mantido ativo, e o Spring vai automaticamente gerar e validar tokens CSRF em formulários HTML.

httpSecurity
    .csrf(withDefaults()) // mantém o CSRF habilitado
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll()
        .anyRequest().authenticated()
    )
    .formLogin(withDefaults()) // usa login por formulário
    .logout(withDefaults());

        Nesse caso:

  • Todo formulário POST precisa incluir um campo hidden com o token CSRF.

  • O Spring automaticamente injeta esse token se você usar o Thymeleaf ou Spring Form Taglib, por exemplo:

    <form th:action="@{/login}" method="post">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <!-- campos de login -->
    </form>
    


segunda-feira, 20 de outubro de 2025

Idempotência - A necessidade de garantir integridade em chamadas distribuídas

        Idempotência significa que executar a mesma operação várias vezes tem o mesmo efeito que executá-la uma única vez. Para escrever um CRUD que não permita entradas duplicadas por falha de rede, reenvio automático, duplicidade, etc é preciso atenção na implementação e usar alguns padrões. 

        Podemos usar Chaves NaturaisQuando um recurso já possui um identificador natural único (como CPF, CNPJ, número de contrato, e-mail, matrícula etc.), podemos utilizá-lo como chave principal no cadastro. Isso elimina a possibilidade de duplicidade no nível lógico do sistema.

        Controle de Duplicidade no Banco de Dados: está bem ligada com a primeira opção, já que quando usamos chaves naturais costumamos controlar via banco como PK ou pelo menos unique constraint. Mas aqui também pode estrapolar esse conceito e usar outros campos em constraints, criando padrões que não podem se repetir. Dependendo do caso uma data ou outro campo podem ser usados para criar essa verificação no Banco de Dados, e ao tentar inserir os dois registros irá barrar.

        Outra opção usada é a “Idempotency Key” (chave única por requisição). O cliente gera uma chave única (ex: UUID) e a envia no cabeçalho da requisição, então o servidor armazena o resultado da primeira requisição associada a essa chave e se a mesma chave for usada novamente, o servidor retorna o mesmo resultado, sem processar a operação novamente.

      Onde essa chave é armazenada? No Banco de Dados ou Cache Distribuído (Redis, Memcached, etc.). Dependendo da aplicação, pode-se guardar nos 2 locais. Uma terceira opção seria a sessão do usuário, para aplicações web autenticadas que guardam sessão.

        Para o caso de clique nervoso do usuário, desabilitar o botão no momento do clique até que volte uma resposta também pode ajudar. Isso também vai depender da situação, do sistema, etc., mas sim, para alguns casos, desabilitar o botão enquanto o servidor responde pode resolver muita coisa. 

     E aí, tem mais alguma estratégia para garantir a idempotência? Deixa aí nos comentários...



sábado, 18 de outubro de 2025

Outbox Pattern - Salve antes de qualquer coisa!

        Em sistemas distribuídos, nos microsserviços, algumas transações abrangem vários serviços, e é comum (e recomendado) que cada um deles tenha seu Banco de Dados, e sua forma de tratar a informação.

            O padrão Outbox nada mais é do que salvar os dados em uma tabela antes de enviar para o próximo passo. Desta maneira, caso ocorra uma falha no serviço que tem a mensagem antes dele repassar, os dados estão garantidos. Depois um JOB faz a leitura dos dados e vai enviando para o brocker ou fazendo o que tiver que fazer, podendo tentar novamente quantas vezes necessário. 

       Caso ocorra algum problema, os dados estão no banco para serem reprocessados. Após o processamento ocorrer com sucesso, os dados podem ser apagados com segurança!

            Isso também serve para aliviar a carga do microsserviço, por exemplo, uma API que tem que receber os dados em grande volume e tratá-lo pra enviar para os próximos serviços, poderia receber e guardar rapidamente no Banco de Dados e então um JOB em segundo plano faz essa parte, estando o processo da API livre para próxima requisição.

        Trabalhei em um sistema onde determinada API recebia os dados, um lote de até mil contas, gerava o Tracer ID, e um ID de lote, validava uns 5 campos de cabeçalho, salvava no banco, depois fazia o repasse para o broken, que então o microsserviço consumia, pegava o lote, dividia conta a conta fazendo validações primárias, e enviava para os microsserviços (eram 2, um de cada área da empresa) fazerem o devido processamento conta a conta. Após o processamento de cada conta, cada microsserviço de área, enviava para o microsserviço de resposta o resultado, que era atualizado no banco de dados (aquele primeiro, usado pela API para guardar a entrada). Lá, depois de todo lote processado, o status do lote era alterado para a situação correspondente (sucesso, sucesso parcial, falha, etc). A qualquer momento poderia se consultar o status do lote na API. Nesse caso, os dados da tabela de entrada tinham controle de status e não eram apagados. A necessidade de apagar ou não, varia de acordo com o negócio. Pode ser que seja interessante manter ou não, em outros quem sabe manter por uns dias apenas?

        Pra esse artigo é só! 

sexta-feira, 17 de outubro de 2025

Aplicações Distribuídas - Usando lock distribuído para gerenciar processamento de dados

        Um dos motivos de eu escrever esse Blog, na verdade o principal deles, é eu ter conteúdo pra mim mesmo de uma forma rápida e fácil, pois na nossa área tem coisas que fazemos e depois esquecemos, ou vemos em algum lugar e quando precisamos não conseguimos encontrar. Esse é um desses casos. Essa publicação surgiu de uma palestra que vi do Rafael Ponte no canal da Zup Innovation no youtube que deixo o link no final e aconselho bastante que assistam. Aqui é apenas um resumo para consulta rápida.

         Um problema comum quando trabalhamos com a aplicações distribuídas é quando temos um JOB ou algo do tipo que precisa realizar um determinado processamento de dados e precisamos garantir que esse processamento seja único para cada registro ou linha da tabela.

        Em uma aplicação com uma única instância podemos fazer isso com o synchronized do Java e vida que segue, mas, hoje normalmente trabalhamos com mais de uma instância da aplicação e aí as coisas complicam um pouco. O synchronized resolve para uma única instância mas não tem controle sobre outras execuções, o que nos faz ter que buscar outras soluções.

        Se os dados que precisam ser processados podem ser processados por uma única instância sem criar gargálos, temos por exemplo soluções como:

  •         Eleger um servidor como responsável por executar esse JOB. Essa eu já usei e é bem comum. Pode-se ter uma tabela de parâmetros no banco com o IP de um servidor específico e o JOB checa se o IP cadastrado é o daquela instância, caso seja, ele executa. Pode-se ter uma propriedade no banco (não necessariamente o IP) e outra com mesmo valor em um arquivo de config, ao ler e comparar as duas, sendo iguais, o servidor executa.
  • Usar o próprio banco de dados para gerenciar o lock. Assim, o próprio banco locka os dados enquanto estiverem sendo usados naquela transação. Esse modo basta adicionar uma anotação na consulta:


    
   

        No caso, diretamente no SQL ficaria assim:


        Lembrando que, é preciso garantir que os métos e a atualização sejam eficientes para não locar os registros por muito tempo. Ou seja, o uso do @Transaction precisa ser feito de forma eficiente, ficando apenas a parte necessária da operação dentro da transação. Na Aula o Rafael até mostra uma forma de como fazer isso também.


        Agora, quando os dados precisam ser processados de forma paralela por mais de uma máquina, temos outras soluções a considerar.

  • Uma máquina lê os dados e distribui para as outras processarem. Dependendo do volume de dados e outras variáveis, essa solução era muito usada antes das mensagerias. Uma máquina lê os dados e chama uma outra máquina de uma lista de máquinas disóníveis para executar o processamento. Por exemplo, uma companhia telefônica que precisa realizar o faturamento de clientes tem um JOB que levanta os IDs dos clientes e distribui para outras máquinas processarem o faturamento daqueles clientes. O ponto ruim é que essa "máquina gerente" acumula muita responsabilidade e pode ser um gargalo. Esse trecho de código que levanta os dados só pode rodar em uma única instancia para não duplicar o processamento.
  • Podemos usar soluções de filas e mensagerias. Soluções como Kafka ou outras.
  • Podemos usar o Lock do banco de dados de forma otimizada. Essa é a solução apresentada pelo Rafael. Para isso, acrescenta-se o ao lock do banco a possibilidade de pular os dados locados e pegar o próximo. Vamos ver o que precisa ser adicionado em termos de anotação Java:



        E no SQL diretamente ficaria assim:


        Mais uma vez fica a dica de garantir que o método esteja otimizado, que dentro da tranzação esteja apenas o que precisa estar lá, para que o lock dure apenas o necessário.

        Essa solução é muito interessante porque permite que várias instâncias executem o mesmo código garantindo que os dados serão respeitados por todas elas como mostra a imagem abaixo:



        Aqui temos todo o código de exemplo trazido por ele na aula:



        Aconselho assistir a aula, tem mais coisas interesantes lá!


Link: aula do Rafael Ponte sobre Distributed Scheduling





quinta-feira, 9 de outubro de 2025

Versões do OpenJDK para download

         As vezes pra quem está começando pode ser complicado achar onde baixar as versões do Java, então deixei aqui o link que vai direto para a página de downloads do OpenJDK. Basta clicar AQUI.

        Infelizmente, com as diretrizes da Oracle, as versões mais antigas do Java vão ficando indisponíveis mesmo nessa página, como é o caso da versão 8 que até a data de hoje (09/10/2025) ainda é muito usada porém não aparece mais para download. Com isso, é preciso uma busca mais cuidadosa e demorada caso precise de uma versão tão antiga. Talvez ache apenas de outros distribuidores. Sempre pesquise bem antes de confiar!

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 reiniciar seu Kafka

# Exemplo para iniciar Kafka:


/opt/kafka/bin/kafka-server-stop.sh


while ps ax | grep -i 'kafka\.Kafka' | grep -v grep > /dev/null; do

    echo "Aguardando o Kafka encerrar..."

    sleep 1

done


/opt/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.