Testando integrações com S3
Prerrequisitos
- Java 17
- Docker
(Apenas se quiser rodar localstack local como um projeto separado)
- Python 3.6+
- PIP (python package manager)
- Conta AWS (pode ser a conta free)*
- Ter a conta da AWS é opcional. Você só precisará dela se quiser testar uma integração real. Caso não seja o seu caso, pode pular para segunda parte
Preparando o ambiente para rodar com a conta real
Caso você tenha uma conta AWS, crie um bucket S3 e depois crie as seguintes variáveis de ambiente:
export ACCESS_KEY="SUA_ACCESS_KEY_AQUI"
export SECRET_KEY="SUA_SECRET_KEY_AQUI"
export BUCKET_NAME="NOME_DO_SEU_BUCKET"
Isso fará com que a aplicação identifique suas credenciais para conectar-se com sua conta AWS e também saiba qual o bucket que deverá utilizar.
Preparando a aplicação
Para subir a aplicação na sua máquina local, primeiro é necessário subir o banco.:
docker-compose up
Assim que o banco subir na porta 3306
, basta rodar:
/gradlew bootRun
A aplicação subirá na porta 8080
. Pronto, já podemos começar a fazer testes. Nesse primeiro momento vamos fazer um teste manual.
Vou utilizar o Postman por ser uma ferramenta bastante conhecida, mas fique a vontade de utilizar outra ferramenta de sua preferência.
Faremos então um POST
em /s3/books
. Precisamos passar dois objetos: um arquivo (imagem) e um json com os dados do livro que
iremos cadastrar.
Após executar a requisição, você deve receber um status 201
e um corpo de resposta parecido com o a seguir:
{
"id": 16,
"title": "selenium",
"author": "Rafael",
"bookCategory": null,
"imagePath": "cover/simple-cdc.png"
}
Repare no último campo da resposta imagePath
. Esse campo é preenchido automaticamente pela API com o caminho onde a imagem foi salva. Isso quer dizer que nossa imagem foi salva no S3 com sucesso. Se quiser, você pode abrir o console da AWS
e verificar se o arquivo realmente foi salvo na sua conta.
O problema
Até aqui fizemos um teste manual com nossa aplicação rodando localmente, mas fazendo a integração real com a AWS. Poderíamos agora fazer uma automação para isso e seria algo bem simples, mas existem algumas questões importantes que devemos considerar:
- Ao utilizar a integração real, mesmo que seja numa conta de teste, estamos consumindo um serviço AWS e isso tem um custo;
- Precisamos ter essa infra sempre em pé. Qualquer manutenção ou alteração no ambiente pode acabar atrapalhando o resultado dos testes;
- Alguém desavisado, pode fazer alterações em alguma configuração (excluir um bucket, por exemplo) e isso também pode atrapalhar nossos resultados.
E como podemos nos prevenir isso e ainda ter uma confiança nos nossos testes? Usando o LocalStack.
O que é o LocalStack?
Utilizando a definição do próprio site do LocalStack:
LocalStack is a cloud service emulator that runs in a single container on your laptop or in your CI environment. With LocalStack, you can run your AWS applications or Lambdas entirely on your local machine without connecting to a remote cloud provider! Whether you are testing complex CDK applications or Terraform configurations, or just beginning to learn about AWS services, LocalStack helps speed up and simplify your testing and development workflow.
Resumindo, é uma ferramenta que nos permite simular serviços da AWS através de containeres de maneira bem simples.
Instalando o LocalStack
É aqui que entra a necessidade de termos o python instalado:
python3 -m pip install localstack
Depois da instalação, basta dar um start nos serviços:
localstack start
Você deverá ver um resultado parecido com a imagem a seguir:
Pronto! Seu ambiente AWS local está pronto para ser utilizado. O que precisamos agora é "dizer" para a nossa aplicação que ela deve apontar para o nosso serviço local e não mais para a AWS real, claro, que isso apenas nos testes.
Para não haver confusão de configurações, criei um arquivo application-local.yml
que é uma cópia
do application.yml
principal. O primeiro terá a configuração para o localstack enquanto o segundo terá a configuração de
produção.
Veja um pedaço do arquivo application-local-yml
onde temos as configurações da AWS:
aws:
accessKey: test
secretKey: test
region: "us-east-1"
s3:
url: http://s3.localhost.localstack.cloud:4566
bucket: mybucket
Os valores aqui de access_key
e secret_key
são valores fake. O mais importante é o valor do campo url
que está apontando
para o S3 do LocalStack.
Ok, mas como vamos fazer para rodar uma configuração ou outra? A resposta é: utilizando
profiles. No momento da subida da aplicação vamos passar um parâmetro a mais onde dizemos que iremos
utilizar um profile local
. Isso basta para que o Spring entenda que deve utilizar o arquivo application-local
e não o
arquivo principal.
./gradlew bootRun --args='--spring.profiles.active=local'
Se refizermos agora a mesma requisição anterior quando ainda usamos a configuração de produção, devemos ter um erro. Isso acontece porque não criamos um bucket no nosso LocalStack. Podemos fazer isso de maneira bem simples utilizando o terminal.
aws s3 mb s3://mybucket --endpoint-url=http://localhost:4566/
Para entender o comando acima:
- aws s3: dizemos qual serviço AWS vamos utilizar;
- mb: comando para criação de um bucket
- s3://<nome_bucket>: caminho do novo bucket (você pode escolher um nome diferente)
- e por fim a url apontando para o serviço LocalStack.
Agora podemos refazer a requisição que o resultado será de sucesso. E como podemos ver se realmente a imagem foi gravada no bucket?
aws s3 ls s3://mybucket/cover/ --endpoint-url=http://localhost:4566/
Nesse comando temos algumas coisas novas. Primeiro utilizamos o comando ls
que lista os
objetos. Por fim, informamos além do bucket a key cover
que faz parte do caminho da imagem.
O resultado deve ser algo assim:
2022-07-07 12:58:18 13853 simple-cdc.png
Teste manual concluído com sucesso.