Criando uma API OCR com FaaS na Azure - Parte 2: Persistindo dados no Azure Postgres SQL com Boas Práticas
Na primeira parte dessa série, a gente construiu uma Function App que recebe uma imagem por HTTP e salva no Azure Blob Storage de forma segura, usando identidade gerenciada. Agora vamos dar o próximo passo e registrar no banco de dados os metadados dessas imagens. Spoiler: vamos usar Azure Postgres SQL, arquitetura em camadas e boas práticas como SOLID e separação de responsabilidades. Por que salvar no banco? O Blob Storage é ótimo para guardar arquivos, mas e se você quiser saber: Quando um arquivo foi enviado? Qual é a URL pública da imagem? Qual é o status de processamento OCR (pendente, processado, com erro)? Quais imagens são receitas médicas, por exemplo? Paara isso que entra o Azure Postgres SQL no jogo. A ideia aqui é manter o controle de tudo o que está rolando com cada imagem. Atualização da arquitetura do projeto Adicionei o IImageRepository.ts para ajustar o dominio relacionado ao banco de dados e sua implementação em OcrImageRepository.ts: /ocr-function-app ├── application/ │ └── UploadImageService.ts ├── domain/ │ └── IImageStorage.ts │ └── IImageRepository.ts ├── infrastructure/ │ └── AzureBlobStorage.ts │ └── OcrImageRepository.ts ├── validations/ │ └── ContentTypeValidator.ts ├── HttpAddToBlob/ │ └── index.ts │ └── function.json ├── constants.ts ├── host.json ├── local.settings.json └── package.json A ideia é manter a pegada de DDD light, delegando responsabilidades para camadas mais específicas. Validando o tipo de conteúdo Antes de sair processando tudo o que chega na Function, bora validar se o conteúdo é uma imagem de verdade. primeiro criamos um enum para guardar os tipos utilizados: export enum AllowedContentTypes { JPEG = 'image/jpeg', PNG = 'image/png', JPG = 'image/jpg', } E criamos uma classe simples para isso: import { AllowedContentTypes } from "../constants"; export class ContentTypeValidator { private static allowedTypes = Object.values(AllowedContentTypes); static validate(contentType?: AllowedContentTypes): void { if (!contentType || !this.allowedTypes.includes(contentType)) { throw new Error('Tipo de conteúdo não suportado. Envie uma imagem JPEG ou PNG.'); } } } E lá dentro da Function: const contentType = req.headers['content-type']; ContentTypeValidator.validate(contentType); Criando a tabela no banco Com o banco de dados criado, execute esse script via Azure Data Studio ou SSMS pra criar a tabela: CREATE TABLE OcrImages ( Id INT IDENTITY PRIMARY KEY, FileName NVARCHAR(200) NOT NULL, Url NVARCHAR(MAX) NOT NULL, UploadDate DATETIME NOT NULL DEFAULT GETDATE(), Status NVARCHAR(50) NOT NULL DEFAULT 'pending', IsPrescription Boolean NOT NULL DEFAULT false );

Na primeira parte dessa série, a gente construiu uma Function App que recebe uma imagem por HTTP e salva no Azure Blob Storage de forma segura, usando identidade gerenciada.
Agora vamos dar o próximo passo e registrar no banco de dados os metadados dessas imagens. Spoiler: vamos usar Azure Postgres SQL, arquitetura em camadas e boas práticas como SOLID e separação de responsabilidades.
Por que salvar no banco?
O Blob Storage é ótimo para guardar arquivos, mas e se você quiser saber:
- Quando um arquivo foi enviado?
- Qual é a URL pública da imagem?
- Qual é o status de processamento OCR (pendente, processado, com erro)?
- Quais imagens são receitas médicas, por exemplo?
Paara isso que entra o Azure Postgres SQL no jogo. A ideia aqui é manter o controle de tudo o que está rolando com cada imagem.
Atualização da arquitetura do projeto
Adicionei o IImageRepository.ts
para ajustar o dominio relacionado ao banco de dados e sua implementação em OcrImageRepository.ts
:
/ocr-function-app
├── application/
│ └── UploadImageService.ts
├── domain/
│ └── IImageStorage.ts
│ └── IImageRepository.ts
├── infrastructure/
│ └── AzureBlobStorage.ts
│ └── OcrImageRepository.ts
├── validations/
│ └── ContentTypeValidator.ts
├── HttpAddToBlob/
│ └── index.ts
│ └── function.json
├── constants.ts
├── host.json
├── local.settings.json
└── package.json
A ideia é manter a pegada de DDD light, delegando responsabilidades para camadas mais específicas.
Validando o tipo de conteúdo
Antes de sair processando tudo o que chega na Function, bora validar se o conteúdo é uma imagem de verdade.
primeiro criamos um enum para guardar os tipos utilizados:
export enum AllowedContentTypes {
JPEG = 'image/jpeg',
PNG = 'image/png',
JPG = 'image/jpg',
}
E criamos uma classe simples para isso:
import { AllowedContentTypes } from "../constants";
export class ContentTypeValidator {
private static allowedTypes = Object.values(AllowedContentTypes);
static validate(contentType?: AllowedContentTypes): void {
if (!contentType || !this.allowedTypes.includes(contentType)) {
throw new Error('Tipo de conteúdo não suportado. Envie uma imagem JPEG ou PNG.');
}
}
}
E lá dentro da Function:
const contentType = req.headers['content-type'];
ContentTypeValidator.validate(contentType);
Criando a tabela no banco
Com o banco de dados criado, execute esse script via Azure Data Studio ou SSMS pra criar a tabela:
CREATE TABLE OcrImages (
Id INT IDENTITY PRIMARY KEY,
FileName NVARCHAR(200) NOT NULL,
Url NVARCHAR(MAX) NOT NULL,
UploadDate DATETIME NOT NULL DEFAULT GETDATE(),
Status NVARCHAR(50) NOT NULL DEFAULT 'pending',
IsPrescription Boolean NOT NULL DEFAULT false
);