Este artigo faz parte da série “Jornada Oracle FreeStack”. No artigo anterior, blindamos nossa aplicação com OCI Vault. Hoje, vamos resolver o problema de armazenamento de arquivos sem sobrecarregar nosso servidor Java.
Quando construímos APIs, é comum criar um endpoint que recebe um arquivo, lê os bytes e salva em disco ou banco.
O Problema: Isso consome memória RAM preciosa (especialmente nas instâncias ARM Always Free), ocupa banda de rede e cria um gargalo. Se 50 usuários fizerem upload simultâneo de imagens de 5MB, seu servidor pode sofrer um “Out of Memory” ou ficar lento para responder outras requisições.
A Solução: PAR (Pre-Authenticated Requests)
A estratégia moderna é delegar. O backend não toca nos bytes da imagem. Em vez disso:
- O frontend pede permissão ao Java para fazer um upload.
- O Java gera uma URL temporária e segura (PAR) da Oracle Cloud.
- O frontend faz o
PUTda imagem diretamente para o Object Storage da Oracle.
Isso significa escalabilidade infinita com consumo zero de recursos no seu micro-backend.
Passo 1: Configuração na Oracle Cloud (OCI)
Diferente de uma pasta no servidor, o Object Storage é um serviço de armazenamento de objetos altamente disponível e escalável.
1.1. Criar o Bucket
- No menu lateral da OCI, vá em Armazenamento (Storage) > Buckets.
- Clique em Criar bucket.
- Nome do bucket:
freestack-bucket. - Camada de armazenamento padrão: Selecione Padrão (Standard). Isso garante que as imagens sejam servidas instantaneamente.
- Ativar controle de versão do objeto: Marque como Ativado. Isso permite recuperar versões anteriores de uma imagem.
- Criptografia: Selecione Criptografar usando chaves gerenciadas pela Oracle.
- Clique em Criar bucket.

É Grátis? (Limites do Always Free)
O Object Storage da Oracle é um dos mais generosos do mercado para desenvolvedores:
- Espaço: 20 GB gratuitos para sempre (Object e Archive Storage combinados).
- Requisições: Até 50.000 chamadas de API por mês.
- Transferência: 10 TB de saída de dados (Egress) por mês.
Dica de Segurança Financeira: Diferente da AWS, onde o excesso de uso é cobrado automaticamente no seu cartão (o que pode gerar faturas inesperadas), na conta Always Free da Oracle o serviço simplesmente para de responder se o limite for atingido. Você tem uma “trava de segurança” nativa: ou é grátis, ou não funciona. Não há cobranças surpresa sem que você faça o upgrade manual da conta.
Bônus: Tornando as Imagens Visíveis no Navegador
Por padrão, a Oracle cria buckets privados. Para que seu blog consiga exibir a imagem via URL direta, precisamos ajustar a visibilidade:
- No Console OCI, entre no seu bucket e vá em Ações > Editar visibilidade.
- Escolha a opção Público.
- Segurança Máxima: Certifique-se de que a opção “Permitir que os usuários listem objetos deste bucket” esteja DESMARCADA.
Isso permite que qualquer pessoa com o link direto veja a imagem, mas ninguém consiga “espiar” a lista de arquivos que você tem lá dentro.
1.2. Coleta de Dados para o .env
Dentro da página de detalhes do seu Bucket recém-criado, localize e anote:
- Namespace: Clique em Detalhes do Bucket. O Namespace é uma string única da sua conta (ex:
axf123abc). Ele aparece logo no início da página de informações. - Region: O código da sua região (ex:
sa-saopaulo-1).
1.3. Políticas de Identidade (A “Chave” do Cofre)
Para que sua aplicação tenha permissão de criar links de upload (PAR), você precisa de uma política no IAM da Oracle. Use o Construtor de Políticas para facilitar:
- No menu lateral da OCI, vá em Identidade e Segurança > Políticas.
- Clique em Criar política.
- Em Casos de uso da política, selecione Gerenciamento de Armazenamento.
- Em Modelos de política comuns, selecione: “permitir que administradores do Serviço Object Storage gerenciem buckets e objetos”.
- Selecione o seu Grupo e o seu Compartimento.
- Clique em Criar.

🔒 Segurança e Boas Práticas: O Risco do Bucket Público
Embora tenhamos desativado a listagem de objetos, um bucket público ainda oferece riscos se os nomes dos arquivos forem previsíveis.
O Problema dos IDs Sequenciais
Se usarmos article-1.png, article-2.png, um atacante pode facilmente criar um script para baixar todas as imagens do seu servidor (Scraping). Pior ainda: se você subir um arquivo sensível por engano no mesmo bucket, ele estará exposto.
A Solução: UUIDs e Nomes Aleatórios
Em sistemas de produção, a boa prática é nunca usar IDs sequenciais na URL. No nosso projeto, implementamos um prefixo, mas o ideal seria:
- Usar UUIDs: Ex:
covers/7b2e-4f1a-9c3d.png. - Bucket Dedicado: Use um bucket exclusivo para mídia pública e outro (privado) para documentos sensíveis.
Passo 2: Implementação no Quarkus
2.1. Dependências (pom.xml)
Utilizamos o BOM do SDK da Oracle para garantir compatibilidade entre os módulos e adicionamos a dependência de Object Storage:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-bom</artifactId>
<version>3.80.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-objectstorage</artifactId>
</dependency>
</dependencies>
2.2. Configuração (application.properties e .env)
Mapeamos os identificadores no application.properties:
# OCI Object Storage Configuration
oci.objectstorage.bucket=${OCI_OBJECTSTORAGE_BUCKET}
oci.objectstorage.namespace=${OCI_OBJECTSTORAGE_NAMESPACE}
oci.objectstorage.region=${OCI_OBJECTSTORAGE_REGION}
Identificadores vs. Segredos: O que vai para o .env?
Seguindo o padrão Zero Trust que estabelecemos no artigo sobre Vault, nosso arquivo .env conterá apenas identificadores.
- O que fica no .env: Nome do bucket, namespace e região. Esses são apenas “endereços”. Saber o nome do seu bucket não dá acesso aos arquivos.
- O que NÃO fica no .env: Chaves privadas ou senhas. O acesso ao bucket é garantido pela identidade da aplicação.
Alimente seu arquivo .env com os dados coletados no Passo 1:
OCI_OBJECTSTORAGE_BUCKET=freestack-bucket
OCI_OBJECTSTORAGE_NAMESPACE=seu_namespace
OCI_OBJECTSTORAGE_REGION=seu_codigo_regiao
Passo 3: O Código da Infraestrutura
3.1. OciObjectStorageService.java
Este serviço orquestra a interação com o SDK da Oracle. Note o uso de nomes de variáveis significativos e a grafia Preauthenticated (minúsculo):
@ApplicationScoped
public class OciObjectStorageService {
private static final Logger LOG = Logger.getLogger(OciObjectStorageService.class);
@ConfigProperty(name = "oci.objectstorage.bucket")
String bucketName;
@ConfigProperty(name = "oci.objectstorage.namespace")
Optional<String> namespaceName;
@ConfigProperty(name = "oci.objectstorage.region")
String region;
public String createUploadPar(String objectName) {
LOG.info("Creating upload PAR for object: " + objectName + " in bucket: " + bucketName);
try (ObjectStorage objectStorageClient = createObjectStorageClient()) {
String namespace = namespaceName.orElseGet(() -> getNamespace(objectStorageClient));
CreatePreauthenticatedRequestDetails parDetails = CreatePreauthenticatedRequestDetails.builder()
.name("Upload-" + objectName + "-" + System.currentTimeMillis())
.accessType(CreatePreauthenticatedRequestDetails.AccessType.ObjectWrite)
.objectName(objectName)
.timeExpires(Date.from(Instant.now().plus(1, ChronoUnit.HOURS)))
.build();
CreatePreauthenticatedRequestRequest parRequest = CreatePreauthenticatedRequestRequest.builder()
.namespaceName(namespace)
.bucketName(bucketName)
.createPreauthenticatedRequestDetails(parDetails)
.build();
CreatePreauthenticatedRequestResponse parResponse = objectStorageClient.createPreauthenticatedRequest(parRequest);
String accessUri = parResponse.getPreauthenticatedRequest().getAccessUri();
String fullPreauthenticatedRequestUrl = String.format("https://objectstorage.%s.oraclecloud.com%s", region, accessUri);
LOG.debug("PAR successfully created: " + fullPreauthenticatedRequestUrl);
return fullPreauthenticatedRequestUrl;
} catch (Exception e) {
LOG.error("CRITICAL ERROR: Could not create Pre-authenticated Request in OCI Object Storage", e);
throw new RuntimeException("Error creating PAR for upload. Check OCI permissions and configuration.");
}
}
public String getObjectUrl(String objectName) {
String namespace = namespaceName.orElse("seu_namespace");
return String.format("https://objectstorage.%s.oraclecloud.com/n/%s/b/%s/o/%s",
region, namespace, bucketName, objectName);
}
}
3.2. O DTO de Resposta (ParResponse.java)
Para devolver as URLs de forma estruturada ao frontend, usamos um Java Record:
public record ParResponse(String parUrl, String objectUrl) {}
Passo 4: Integrando ao Negócio (Articles)
No nosso ArticleService, ao gerar o link de upload, já atualizamos o documento JSON do artigo com a URL final. Note a separação de responsabilidades e o uso de métodos auxiliares.
4.1. Refatorando o ArticleService.java
@ApplicationScoped
public class ArticleService {
@Inject ArticleRepository repository;
@Inject OciObjectStorageService objectStorageService;
@Inject ObjectMapper objectMapper;
@Transactional
public ParResponse generateCoverPar(Long articleId, String fileName) {
Article article = repository.findById(articleId);
if (article == null) {
throw new NotFoundException("Article not found with id: " + articleId);
}
String fileExtension = getFileExtension(fileName);
String storageObjectName = "covers/article-" + articleId + fileExtension;
// Solicita a PAR e a URL pública
String parUrl = objectStorageService.createUploadPar(storageObjectName);
String objectPublicUrl = objectStorageService.getObjectUrl(storageObjectName);
// Atualiza o JSON do artigo
updateArticleContentWithCoverUrl(article, objectPublicUrl);
return new ParResponse(parUrl, objectPublicUrl);
}
private String getFileExtension(String fileName) {
if (fileName != null && fileName.contains(".")) {
return fileName.substring(fileName.lastIndexOf("."));
}
return ".png";
}
private void updateArticleContentWithCoverUrl(Article article, String coverUrl) {
if (article.content != null && article.content.isObject()) {
((ObjectNode) article.content).put("coverUrl", coverUrl);
} else {
ObjectNode newContent = objectMapper.createObjectNode();
newContent.put("coverUrl", coverUrl);
article.content = newContent;
}
}
}
4.2. O Endpoint REST (ArticleResource.java)
Expomos a rota para o frontend solicitar o link de upload:
@POST
@Path("/{id}/cover-upload-url")
public ParResponse getUploadUrl(@PathParam("id") Long id, @QueryParam("fileName") String fileName) {
return service.generateCoverPar(id, fileName != null ? fileName : "cover.png");
}
Passo 5: Testando o Fluxo Completo
Vamos simular o upload de uma imagem real chamada linux-open-source.png.
1. Criar o Artigo
curl -X POST http://localhost:8080/articles
-H "Content-Type: application/json"
-d '{
"title": "Ataque Open Source: O Poder do Linux",
"author": "Matheus Oliveira",
"content": {
"body": "Explorando a arquitetura convergente...",
"tags": ["linux", "open-source", "oci"]
}
}'
Suponha que o ID retornado foi 1053.
2. Solicitar Autorização de Upload (PAR)
curl -X POST "http://localhost:8080/articles/1053/cover-upload-url?fileName=linux-open-source.png"
Resposta do Servidor:
{
"parUrl": "https://objectstorage.sa-saopaulo-1.oraclecloud.com/p/ABC123XYZ.../covers/article-1053.png",
"objectUrl": "https://objectstorage.sa-saopaulo-1.oraclecloud.com/n/seu_namespace/b/freestack-bucket/o/covers/article-1053.png"
}
3. Executar o Upload Direto para a Oracle
O computador (ou frontend) envia a imagem diretamente para a nuvem da Oracle, usando a parUrl:
curl -X PUT -H "Content-Type: image/png"
--data-binary "@./images/linux-open-source.png"
"URL_DA_PAR_AQUI"
4. Visualizar a Imagem no Navegador
Acesse a objectUrl retornada no passo 2 diretamente no seu navegador:
https://objectstorage.sa-saopaulo-1.oraclecloud.com/n/seu_namespace/b/freestack-bucket/o/covers/article-1053.png
Conclusão: Arquitetura de Elite
Ao delegar o armazenamento para o Object Storage, você garante que sua aplicação suporte milhares de uploads simultâneos sem nunca degradar a performance da API. Suas imagens agora residem em uma infraestrutura global, prontas para serem servidas de forma ultra-rápida e segura.
No próximo artigo, entraremos no mundo do OCI Notifications (ONS) para processar eventos de forma assíncrona no Always Free.
