Desvendando o Segredo: Como Implementar File Upload com Spring Boot e Amazon S3
Tags: aws, spring, java, amazons3
Published: 2024-06-20 23:29:16

Não é de hoje que empresas e também pessoas tem necessidade de armazenar e gerenciar arquivos de forma segura e eficiente. Um bom exemplo disso é que até hoje existe uma alta procura por produtos como SDD, Pendrives, HDD externos. Sem falar que empresas como Amazon, Google, Apple, Microsoft vendem armazenamento em cloud em diferentes escalas e preços. 

Storage de arquivo pode atender clientes em diversas categorias, desde os usuários comuns por meio de produtos como Google Drive, One Drive e ICloud. Até mesmo empresas de todos os tamanhos, com a necessidade de utilizar sistemas customizados para gerenciar seus arquivos.  

Os clientes da Cloud Amazon Web Service, podem escrever suas aplicações para se integrar ao [Amazon Simple Storage Service (S3)](https://aws.amazon.com/pt/s3/), o serviço de armazenamento distribuído seguro e escalável, que pode ser acessado de diferentes aplicativos e plataformas por meio de API's.

Hoje é possivel escrever aplicações nas mais diversas linguagens e plataformas, utilizando o SDK da Amazon para gerenciar seus arquivos. E para nós javeiros não é diferente, podemos nos basear no SDK ou utilizar algum framework como Micronaut, Quarkus e Spring, para nos auxiliar neste processo.

## E a pergunta que não quer calar? Como Implemento um FileUploader com Spring Boot e Amazon S3? 

A solução deste problema é dividida em duas etapas, a primeira etapa é onde iremos realizar a configuração da aplicação para utilizar o Amazon S3 em um projeto Spring. E por fim, será construído o serviço responsável por fazer o upload dos arquivos no bucket.

### 1 - Configurando a Aplicação para Utilizar o Amazon S3

Então, vamos lá! O [Spring Cloud AWS](https://docs.awspring.io/spring-cloud-aws/docs/3.0.0/reference/html/index.html#using-amazon-web-services) é um módulo construído para abstrair o uso de serviços da AWS para aplicativos Spring Boot. Inclusive existe um starter para aplicações que utilizaram apenas o modulo para uso do Amazon S3, se trata do [Spring Cloud AWS S3](https://docs.awspring.io/spring-cloud-aws/docs/3.0.0/reference/html/index.html#spring-cloud-aws-s3), que oferecem a diferentes formas de acessar e utilizar o Bucket no sistema, e também conta com a abstração `S3Template` que torna simples e intuitivo o ato de executar operações de consulta, inserção e deleção no S3. Então o primeiro passo é adicioná-la nas dependências do projeto. 

Em seguida, iremos trabalhar na configuração do Spring Cloud AWS S3, a fim de permitir que o mesmo faça o acesso ao bucket no S3, para isso devemos informar o Endpoint de acesso ao bucket, a região que o bucket esta disponível, e as credenciais de acesso à conta da AWS. 

```properties
spring.cloud.aws.endpoint=${AWS_ENDPOINT:http://s3.localhost.localstack.cloud:4566} 
spring.cloud.aws.region.static=${AWS_REGION:us-east-1}
spring.cloud.aws.credentials.access-key=${AWS_ACCESS_KEY:localstackAccessKeyId}
spring.cloud.aws.credentials.secret-key=${AWS_SECRET_KEY:localstackSecretAccessKey}
```

No código acima, iniciamos definindo o endpoint de acesso ao bucket, através da propriedade `spring.cloud.aws.endpoint`. Em seguida, definimos que a região em que o bucket se encontrar por via da propriedade `spring.cloud.aws.region.static`, por fim definimos as credenciais de acesso à conta através das propriedades `spring.cloud.aws.credentials.access-key` e `spring.cloud.aws.credentials.secret-key`.

### 2 - Escrevendo o Serviço de Upload de Arquivo

Nesta etapa vamos definir o código responsável por receber um arquivo e fazer o upload dele no Amazon S3. Antes de iniciar a construção é preciso entender como funciona a postagem de um arquivo. No Amazon S3 você pode definir um bucket. Eu ouvi certo? Um balde? Sim, no S3 o bucket é um tipo de diretório que irá armazenar seus objetos, que podem ser arquivos, imagens, musicas, e qualquer outro tipo de objeto independente do seu  tipo de mídia. Esse objeto deve ter uma chave única de acesso (Key), que irá ser utilizada para geração de links de download, atualização e deleção do objeto.

Então iremos iniciar o processo, definindo a abstração responsável por representar um Arquivo. Em seguida, definiremos o `bean` `FileUploadService` que será responsável por gerenciar as operações sobre os Arquivos. E por fim, iremos construir o método responsável por fazer Upload no bucket.


```Java
public record FileUpload(MultipartFile data){}
```

O código apresentado acima, define uma abstração que representa um arquivo que irá ser persistido no storage de arquivos. O FileUpload tem um único campo do tipo [`MultipartFile`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html) que é uma abstração do Spring Web para representar um arquivo que foi submetido através de uma requisição web.


Após a definição da abstração seguiremos para definição do serviço de upload. 

```java
@Service
public class FileStorageService {
    @Autowired
    private S3Template template;
  
    private String bucket = "myBucketName";

    public String upload(FileUpload fileUpload) {
 
        try (var file = fileUpload.data().getInputStream()) {
            String key = UUID.randomUUID().toString();
            S3Resource uploaded = template.upload(bucket, key, file);
            return key;
        } catch (IOException ex) {
            throw new RuntimeException("Não foi possivel realizar o upload do documento");
        }
    }
}
```

O código pode ser resumido da seguinte forma, primeiro definimos a classe **FileStorageService** como um `bean` de serviço, através da anotação `@Service`, em seguida, injetamos o S3Template através do campo template previamente anotado com `@Autowired`. Em seguida, é definido o método upload, que retorna a String referente a chave de consulta do arquivo no Bucket, e recebe o FileUpload que será persistido.  

A lógica de upload do arquivo é um pouco sensível, dado que manipular arquivos em Java exigem que o arquivo seja fechado, o que é muito fácil de ser esquecido, e pequenos erros, sutis como este, levam a problemas de vazamento de memória que podem prejudicar o funcionamento da aplicação.

Dado a esta exigência, foi utilizado a instrução [try-with-resource] (https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html), que tenta abrir o arquivo dentro da cláusula try, caso tenha sucesso, o mesmo é fornecido para uso dentro do escopo da instrução e fechá-lo automaticamente quando a execução for finalizada. Caso contrario podemos fazer um tratamento de exceção personalizado com as instruções catch. No escopo do bloco try, é onde a mágica acontece, então definimos uma chave de acesso única para arquivo, e utilizamos o método upload do S3Template, informando o bucket de destino, a chave de acesso, e o inputStream do arquivo, caso nenhuma IOException  não seja lançada, retornamos a chave de acesso.


### Conclusão

Armazenar e gerenciar arquivos é uma necessidade de muitas pessoas e empresas, não é à toa que os grandes players do mercado como Amazon, Google, Apple, Microsoft e outros, vem lucrando com a venda dos seus produtos de Storage.

É comum que empresas de todos os tamanhos precisem definir processos e logicas customizadas para o armazenamento e uso de seus arquivos, e uma boa solução é o desenvolvimento de um software que cuide de garantir as regras e comportamentos para o gerenciamento dos seus arquivos, que serão armazenados em um serviço storage distribuído como Amazon S3.

O Spring Cloud AWS é um módulo focado em abstrair o uso de serviços da Cloud AWS para aplicações Spring, e contém um starter que facilita a integração com os buckets do S3. A construção de um serviço que manipule arquivos deve ser feita com cuidado, já que conforme os arquivos são abertos, os mesmos devem ser fechados, para não ocasionar vazamentos de memória. Porém, podemos mitigar isso, utilizando o try-with-resource, que abstrai o processo de fechamento do arquivo.