Caio Bomfim Godoy
Em andamentobackendtestesarquiteturasre

REST Countries API

API intermediária em Java 21/Spring Boot 3 para consumo da REST Countries API pública, com foco em práticas de SRE e DevOps — observabilidade, resiliência, infraestrutura como código e evolução incremental por releases.

dezembro de 2024

Capa do projeto REST Countries API

Contexto

O Journey Lab é uma iniciativa pessoal de evolução técnica com foco na migração para áreas de SRE e Cloud/DevOps. O REST Countries API é o primeiro projeto dessa jornada, simulando um cenário real de integração com serviço externo para aplicar boas práticas de observabilidade, resiliência, infraestrutura como código, automação e escalabilidade.

O projeto foi desenvolvido de forma incremental, seguindo um modelo de evolução por releases — semelhante ao fluxo de times de engenharia maduros — com cada versão adicionando uma camada de qualidade e maturidade operacional ao sistema.

Problema

Consumir APIs externas em sistemas reais apresenta desafios que projetos de estudo convencionais não simulam:

  • Indisponibilidade: a API externa pode estar fora do ar ou com latência alta
  • Testes frágeis: testar contra a API real torna os testes não determinísticos e lentos
  • Acoplamento forte: sem abstração, o sistema inteiro é afetado por mudanças no contrato externo
  • Observabilidade ausente: sem logs e métricas, falhas de integração são invisíveis em produção

A ausência de estratégias para lidar com esses problemas é o que diferencia um projeto de estudo de um sistema pronto para produção.

Solução

Construção de uma API intermediária que consome a REST Countries API pública, aplicando camadas de abstração e qualidade sobre a integração:

  • Camada de cliente isolada via OpenFeign, com mapeamento de erros externos para exceções internas
  • Testes de integração com WireMock para simular a API externa sem depender dela
  • Ambiente de testes isolado e reprodutível com Testcontainers
  • Evolução por releases, cada uma com foco em um aspecto de qualidade específico

Arquitetura

Cliente HTTP
     │
     ▼
CountryController (REST)
     │  GET /countries/{name}
     ▼
CountryServiceImpl (implementa CountryServicePort)
     │  aplica regras e transforma a resposta
     ▼
CountryClient (OpenFeign)
     │  GET /name/{name}
     ▼
REST Countries API (externa) ou WireMock (local/testes)

A separação via interface CountryServicePort isola o contrato da implementação, facilitando testes e substituição de dependências. O CountryClient via OpenFeign é a única fronteira com o mundo externo — em desenvolvimento e CI, o WireMock assume esse papel via Docker Compose.

Arquitetura do Journey Lab REST Countries

API

MétodoEndpointRespostaDescrição
GET/countries/{name}200Retorna informações do país pelo nome

Resposta de sucesso (200 OK):

{
  "name": "Brazil",
  "capital": "Brasilia",
  "region": "Americas",
  "population": 212559409,
  "flagUrl": "https://flagcdn.com/w320/br.png"
}

A URL da API externa é configurável via variável de ambiente COUNTRY_API_URL, permitindo alternar entre o WireMock local e a API real (https://restcountries.com/v3.1) sem alterar o código.

Stack Utilizada

CamadaTecnologia
BackendJava 21, Spring Boot 3.4.8, Spring Cloud 2024.0.2
IntegraçãoSpring Cloud OpenFeign (cliente HTTP declarativo)
Infraestrutura localDocker + Docker Compose, WireMock (container standalone)
UtilitáriosLombok (redução de boilerplate), Spring Boot Actuator (health/métricas)
TestesJUnit 5, Spring Boot Test

Decisões Técnicas

WireMock via Docker Compose (não embarcado nos testes): no v0.1.0, o WireMock roda como um container standalone gerenciado pelo Docker Compose, na porta 10104. A URL é injetada na aplicação via variável de ambiente COUNTRY_API_URL=http://wiremock:8080/v3.1. Isso permite simular a API externa localmente sem alterar código — basta trocar a variável para apontar para a API real. A integração com Testcontainers é planejada para releases futuras.

Interface CountryServicePort: o contrato do serviço é definido em uma interface, desacoplando Controller de implementação. Isso facilita testes unitários com mocks e permite evoluir a implementação sem impactar quem depende do contrato.

Java Records para DTOs: CountryResponse e os DTOs aninhados de CountryApiResponse (incluindo CountryName e CountryFlags) são Java records — imutáveis por padrão, com equals, hashCode e toString gerados automaticamente.

Evolução por releases com intenção clara: em vez de tentar construir o sistema "completo" de uma vez, cada release adiciona uma camada de maturidade com objetivo específico. Isso simula como times de engenharia maduros trabalham e permite aprender de forma estruturada.

Desafios

Configuração do WireMock com cenários dinâmicos: a documentação do WireMock é extensa, mas configurar cenários que simulam timeout real (não apenas delay), conexão encerrada abruptamente e respostas malformadas exigiu experimentação.

Integração Testcontainers + WireMock: fazer o WireMock rodar dentro de um container gerenciado pelo Testcontainers, com a URL dinâmica sendo injetada no Feign Client em runtime, exigiu entender o ciclo de vida dos containers durante os testes.

Tratamento de exceções do Feign: o Feign lança exceções genéricas por padrão. Implementar um ErrorDecoder customizado para converter cada tipo de erro externo em uma exceção de domínio específica foi mais trabalhoso do que o esperado, especialmente para casos como RetryableException (timeout) vs FeignException (erros HTTP).

Aprendizados

Testes de integração são parte do design, não validação posterior. Escrever os testes de integração antes ou junto com o código forçou decisões melhores de arquitetura — especialmente na separação do Feign Client como uma fronteira isolável.

WireMock muda a forma de pensar sobre contratos externos. Ao definir os stubs do WireMock, você é forçado a documentar explicitamente o que a API externa pode retornar (incluindo cenários de erro), o que melhora o entendimento do sistema como um todo.

Observabilidade deve ser planejada desde o início. Adicionar logs estruturados retroativamente é muito mais trabalhoso do que incluí-los na implementação inicial. Esse foi o principal aprendizado que vai guiar as próximas releases do projeto.

Roadmap de Releases

ReleaseObjetivoStatus
0.1API funcional com consumo da REST Countries✅ Concluída
0.2Cache com Caffeine⏳ Planejada
0.3Observabilidade com Micrometer + Prometheus⏳ Planejada
0.4Testes de integração com Testcontainers⏳ Planejada
0.5Dockerização completa⏳ Planejada
0.6Deploy com Terraform em AWS ECS⏳ Planejada
0.7API Gateway + autenticação básica⏳ Planejada
0.8Alertas e dashboards com Grafana/Datadog⏳ Planejada
0.9Resiliência: Retry, Timeout, Circuit Breaker⏳ Planejada
1.0Publicação no GitHub + LinkedIn⏳ Planejada