Construindo Aplicaçõe com Azurite
Neste artigo, vamos explorar um projeto interessante que combina microsserviços, FastAPI, Docker Compose e o Azurite para emular o Azure Storage. Este projeto é ideal para desenvolvedores que desejam aprimorar suas habilidades em arquitetura de microsserviços, integração de APIs e automação com Docker.
Descrição do Projeto
Este é um sistema de gerenciamento de livros desenvolvido em Python usando o framework FastAPI. A solução inclui duas APIs e um worker para processar mensagens em uma fila, aplicando uma arquitetura de microsserviços que se comunica com o Azurite, um emulador local do Azure Storage.
O objetivo principal é gerenciar livros, realizar operações CRUD (Create, Read, Update, Delete), e permitir o envio e processamento de livros através de uma fila de mensagens. Tudo isso é orquestrado pelo Docker Compose, que facilita o setup e a execução dos serviços envolvidos.
O que é o Azurite?
Azurite é um emulador local do Azure Storage que permite que desenvolvedores simulem e testem funcionalidades do Azure Blob, Queue e Table Storage sem precisar de uma conta na nuvem. Criado pela Microsoft, o Azurite é uma alternativa prática para aqueles que desejam desenvolver e testar suas aplicações localmente antes de mover para um ambiente em produção. Ele é especialmente útil para equipes que estão desenvolvendo com metodologias ágeis, permitindo um ciclo de feedback rápido.
Principais Benefícios do Azurite
- Custo Zero: Como o Azurite roda localmente, não há custo associado ao uso do Azure durante o desenvolvimento. É uma maneira eficiente de trabalhar sem incorrer em custos de armazenamento na nuvem.
- Fácil Configuração: Com o uso do Docker, a configuração do Azurite se torna extremamente simples. Você pode ter um ambiente Azure Storage emulado em questão de minutos, com apenas alguns comandos.
- Testes Realistas: O Azurite replica muitas das funcionalidades oferecidas pelo Azure Storage. Isso significa que você pode desenvolver e testar sua aplicação de maneira bastante similar ao ambiente em produção, garantindo que suas integrações com o Azure Storage funcionarão conforme esperado.
- Integração com Docker Compose: O Azurite pode ser facilmente integrado ao Docker Compose, o que permite orquestrar o emulador juntamente com outras partes da aplicação. Isso facilita o desenvolvimento de sistemas complexos que dependem de vários serviços.
Funcionalidades Suportadas
O Azurite suporta as três principais funcionalidades do Azure Storage:
- Blob Storage: Armazenamento de objetos, similar a um sistema de arquivos para grandes quantidades de dados não estruturados.
- Queue Storage: Gerenciamento de filas de mensagens, ideal para cenários assíncronos, como o processamento em background.
- Table Storage: Um armazenamento NoSQL para dados estruturados, perfeito para armazenamento simples de grandes volumes de dados.
O projeto Books faz uso do Queue Storage e do Table Storage, simulados pelo Azurite, para realizar o envio de livros para filas e salvar seus dados em tabelas.
Como Utilizar o Azurite no Projeto
O Azurite está configurado como um dos serviços no arquivo docker-compose.yaml
do projeto. Isso permite que ele seja iniciado juntamente com os demais serviços da aplicação, como a API de livros e o worker que processa as mensagens da fila. Veja abaixo um trecho da configuração do Azurite no Docker Compose:
services:
azurite:
image: mcr.microsoft.com/azure-storage/azurite
ports:
- "10000:10000"
- "10001:10001"
- "10002:10002"
volumes:
- azurite_data:/data
command: "azurite -s -l /data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0"
Neste exemplo, o Azurite expõe as portas 10000, 10001 e 10002 para permitir acesso aos serviços de Blob, Queue e Table Storage, respectivamente.
Assim, com o Azurite, você pode desenvolver suas funcionalidades localmente e garantir que toda a lógica de integração com o Azure Storage esteja devidamente testada antes de fazer o deploy para um ambiente na nuvem.
A organização de diretórios é clara e modular, facilitando a manutenção e expansão da aplicação:
├── README.MD
├── books
│ ├── main.py
│ └── src
│ ├── batch
│ │ └── worker_book
│ │ └── worker.py
│ ├── config
│ │ └── azure_config.py
│ ├── controller
│ │ └── book_controller.py
│ ├── dto
│ │ └── book_dto.py
│ ├── repository
│ │ └── book_repository.py
│ └── services
│ └── book_service.py
Prerequisitos
Antes de começarmos, certifique-se de ter o seguinte instalado e configurado em sua máquina:
- Python 3.7+: Download e Instalação
- Node.js e npm: Necessários para instalar o Azurite. Download e Instalação
- FastAPI e Uvicorn: Serão instalados posteriormente.
- Azurite: Emulador local do Azure Blob Storage.
- cURL: Ferramenta de linha de comando para transferir dados com URLs. Geralmente já está instalada em sistemas Unix; para Windows, pode ser baixada aqui.
Configurando o Azurite
1. Instalação do Azurite
Azurite pode ser instalado globalmente usando o npm. Abra seu terminal e execute:
npm install -g azurite
2. Iniciando o Azurite
Após a instalação, inicie o Azurite em um terminal separado para que ele permaneça em execução enquanto desenvolve sua aplicação:
azurite --silent --location ./azurite --debug ./azurite/debug.log
Parâmetros Explicados:
--silent
: Executa o Azurite sem logs no console.--location
: Define o diretório onde o Azurite armazenará os dados.--debug
: Define o caminho para o arquivo de log de depuração.
Por padrão, Azurite escuta nas seguintes portas:
- Blob Storage:
10000
- Queue Storage:
10001
- Table Storage:
10002
3. Verificando o Funcionamento do Azurite
Para garantir que Azurite está funcionando corretamente, você pode usar o Azure Storage Explorer ou outros clientes compatíveis para se conectar ao emulador local. Utilize as credenciais padrão:
- Account Name:
devstoreaccount1
- Account Key:
Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
- Blob Service Endpoint:
http://127.0.0.1:10000/devstoreaccount1
Vamos explicar cada uma dessas partes:
books/
Este diretório contém a API principal para o gerenciamento de livros. Ele inclui:
- main.py: O ponto de entrada da aplicação FastAPI.
# uvicorn: É um servidor ASGI que executa a aplicação FastAPI.
# FastAPI: Framework usado para construir a API.
# book_controller: Está importando o roteador de rotas do controlador de livros.
import uvicorn
from fastapi import FastAPI
from books.src.controller.book_controller import router as book_router
#Cria uma instância da aplicação FastAPI.
app = FastAPI()
# Registrar as rotas do controlador de livros
app.include_router(book_router, prefix="/api", tags=["books"])
# include_router: Usa o roteador importado para registrar as rotas que estão no book_controller.
# prefix="/api": Define que todas as rotas terão o prefixo /api.
# tags=["books"]: Agrupa as rotas sob a tag "books" na documentação da API.
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
# Esse bloco de código permite rodar o servidor quando o arquivo é executado diretamente.
# host="0.0.0.0": Faz com que o servidor esteja acessível em qualquer interface de rede.
# port=8001: Define que a aplicação será executada na porta 8001.
# Este código define uma API para gerenciar livros e registra as rotas do controlador para responder a solicitações HTTP.
- src/: Diretório com todos os módulos internos, divididos em camadas para facilitar a manutenção e compreensão do código.
- batch/worker_book/worker.py: Worker responsável por processar as mensagens da fila e salvar os dados na tabela do Azure Table Storage. Este worker é essencial para manter o sistema atualizado com os dados enviados para a fila.
import time
from books.src.services.book_service import BookService
# import time: Importa o módulo time, que é uma biblioteca padrão do Python usada para manipulação de tempo, como pausar a execução por um determinado período.
# #from books.src.services.book_service import BookService: Importa a classe BookService do módulo book_service dentro do diretório books/src/services. Essa classe provavelmente contém a lógica de negócio para trabalhar com livros.
def start_worker():
book_service = BookService()
while True:
book_service.process_queue_messages()
time.sleep(10) # Intervalo entre verificações na fila
# book_service = BookService(): Cria uma instância da classe BookService.
# # while True: Inicia um loop infinito. Esse loop faz com que a função continue rodando até que seja interrompida manualmente.
# book_service.process_queue_messages(): Chama o método process_queue_messages() da instância de BookService. Provavelmente, esse método processa mensagens em uma fila — como uma fila de tarefas ou notificações.
# time.sleep(10): Pausa a execução do loop por 10 segundos antes de reiniciar. Isso significa que o worker verifica a fila a cada 10 segundos.
if __name__ == "__main__":
start_worker()
# # if name == "main": Esse bloco de código é executado apenas se o arquivo for executado diretamente, não quando ele é importado como um módulo em outro arquivo.
# start_worker(): Chama a função start_worker(), iniciando o worker que processa as mensagens da fila em um loop contínuo.
# Esse código define um "worker", ou seja, um processo que roda continuamente e realiza uma determinada tarefa em intervalos de tempo. Neste caso, ele está processando mensagens de uma fila por meio da classe BookService. A cada 10 segundos, o método process_queue_messages() é chamado para processar novas mensagens. Esse tipo de worker é útil para lidar com tarefas assíncronas, como processamento em segundo plano ou comunicação com filas de mensagens (ex.: RabbitMQ, AWS SQS).
config/: Contém arquivos de configuração, incluindo azure_config.py
, que define a configuração de conexão com o Azure Storage.
import os
from azure.core.exceptions import ResourceExistsError
from azure.core.pipeline.transport import RequestsTransport
from azure.data.tables import TableServiceClient, TableClient
from azure.storage.queue import QueueServiceClient
# os: Importa a biblioteca padrão do Python para manipulação de variáveis de ambiente e funções do sistema operacional.
# ResourceExistsError: Importado da Azure SDK, usado para tratar o erro quando se tenta criar um recurso que já existe.
# RequestsTransport: Configura o transporte para comunicação HTTP, permitindo modificar o comportamento do SSL.
# TableServiceClient, TableClient: Importados da Azure Tables SDK, são utilizados para gerenciar tabelas e criar/obter clientes de tabelas.
# QueueServiceClient: Importado da Azure Storage SDK, utilizado para gerenciar o serviço de fila.
class AzureConfig:
@staticmethod
def get_queue_service_client():
connect_str = os.getenv("AZURE_STORAGE_QUEUE_CONNECTION_STRING")
if not connect_str:
raise ValueError(
"AZURE_STORAGE_QUEUE_CONNECTION_STRING não está configurado. Certifique-se de definir esta variável de ambiente.")
transport = RequestsTransport(verify=False) # Desabilitar SSL para ambiente local
queue_service_client = QueueServiceClient.from_connection_string(connect_str, transport=transport)
return queue_service_client
# @staticmethod: Indica que o método pode ser chamado diretamente pela classe, sem precisar de uma instância.
# connect_str = os.getenv("AZURE_STORAGE_QUEUE_CONNECTION_STRING"): Obtém a string de conexão do serviço de fila a partir da variável de ambiente.
# # if not connect_str: Verifica se a variável de ambiente está definida. Se não estiver, lança um erro.
# RequestsTransport(verify=False): Cria um transporte que desabilita a verificação de SSL. Útil para testes em ambiente local.
# QueueServiceClient.from_connection_string(connect_str, transport=transport): Cria um cliente para o serviço de filas usando a string de conexão e a configuração do transporte.
# # return queue_service_client: Retorna o cliente de filas criado.
@staticmethod
def get_table_service_client():
connect_str = os.getenv("AZURE_STORAGE_TABLE_CONNECTION_STRING")
if not connect_str:
raise ValueError(
"AZURE_STORAGE_TABLE_CONNECTION_STRING não está configurado. Certifique-se de definir esta variável de ambiente.")
transport = RequestsTransport(verify=False) # Desabilitar SSL para ambiente local
table_service_client = TableServiceClient.from_connection_string(connect_str, transport=transport)
return table_service_client
# Funciona de forma similar ao método anterior, mas cria um cliente para o serviço de tabelas
@staticmethod
def create_table_if_not_exists(table_name):
connect_str = os.getenv("AZURE_STORAGE_TABLE_CONNECTION_STRING")
if not connect_str:
raise ValueError(
"AZURE_STORAGE_CONNECTION_STRING não está configurado. Certifique-se de definir esta variável de ambiente.")
try:
table_client = TableClient.from_connection_string(conn_str=connect_str, table_name=table_name)
table_client.create_table()
print(f"Tabela '{table_name}' criada com sucesso!")
except ResourceExistsError:
print("A tabela já existe.")
except Exception as e:
print(f"Erro ao criar a tabela '{table_name}': {e}")
# create_table_if_not_exists(table_name): Método que cria uma tabela no serviço de tabelas, caso ela não exista.
# connect_str = os.getenv("AZURE_STORAGE_TABLE_CONNECTION_STRING"): Obtém a string de conexão do serviço de tabelas.
# table_client = TableClient.from_connection_string(...): Cria um cliente específico para a tabela usando o nome fornecido.
# table_client.create_table(): Tenta criar a tabela no serviço Azure.
# # except ResourceExistsError: Caso a tabela já exista, exibe uma mensagem indicando isso.
# # except Exception as e: Captura qualquer outra exceção que ocorra e exibe uma mensagem de erro.
# Essa classe AzureConfig é uma camada de abstração para facilitar a interação com os serviços de filas e tabelas da Azure. Ela contém métodos para:
# Criar clientes para filas e tabelas usando strings de conexão armazenadas em variáveis de ambiente.
# Desabilitar SSL para testes em ambientes locais.
# Criar uma tabela caso ela não exista, tratando os erros adequados para garantir que o código não falhe se a tabela já estiver presente.
# Isso é útil em um ambiente corporativo onde filas são usadas para comunicação entre serviços e tabelas são usadas para armazenar dados estruturados.
- controller/: Implementa os controladores da API. O
book_controller.py
define os endpoints para as operações de CRUD de livros.
# Este código define um conjunto de rotas HTTP usando FastAPI para gerenciar recursos relacionados a livros.
# Ele usa um serviço chamado BookService para executar operações de negócios e manipulação dos livros.
from fastapi import APIRouter, HTTPException
from books.src.services.book_service import BookService
from books.src.dto.book_dto import BookDTO
# APIRouter: Usado para definir um roteador que agrupa rotas relacionadas.
# HTTPException: Exceção usada para retornar erros HTTP específicos.
# BookService: Classe que contém a lógica de negócio para manipulação de livros.
# BookDTO: Objeto de transferência de dados (DTO) que define o formato dos dados de um livro.
router = APIRouter()
book_service = BookService()
# router = APIRouter(): Cria um roteador que agrupa as rotas da API para facilitar o gerenciamento.
# book_service = BookService(): Cria uma instância do BookService para ser usada nas operações.
@router.get("/v1/books/query")
def query_books(filter_expression: str):
books = book_service.query_books(filter_expression)
if not books:
raise HTTPException(status_code=404, detail="Nenhum livro encontrado")
return books
# @router.get("/v1/books/query"): Define uma rota GET na URL /v1/books/query.
# filter_expression: Parâmetro de consulta usado para filtrar livros.
# book_service.query_books(filter_expression): Chama o serviço para obter livros que atendem ao filtro.
# HTTPException(404): Lança um erro 404 caso nenhum livro seja encontrado.
@router.get("/v1/books", response_model=list[BookDTO])
def get_all_books():
books = book_service.get_all_books()
if not books:
raise HTTPException(status_code=404, detail="Nenhum livro encontrado")
return books
# @router.get("/v1/books"): Define uma rota GET para listar todos os livros.
# response_model=list[BookDTO]: Retorna uma lista de objetos do tipo BookDTO.
# book_service.get_all_books(): Obtém todos os livros do serviço.
# HTTPException(404): Retorna 404 se não houver livros.
@router.delete("/v1/books/{isbn}")
def delete_book(isbn: str):
try:
book_service.delete_book(isbn)
return {"message": f"Livro com ISBN {isbn} deletado com sucesso."}
except ResourceNotFoundError:
raise HTTPException(status_code=404, detail="Livro não encontrado para deletar")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao deletar o livro: {e}")
# @router.delete("/v1/books/{isbn}"): Define uma rota DELETE para deletar um livro por seu ISBN.
# book_service.delete_book(isbn): Chama o método do serviço para deletar o livro.
# ResourceNotFoundError: Lança erro 404 se o livro não for encontrado.
# HTTPException(500): Lança erro 500 para quaisquer outros problemas durante a exclusão.
@router.put("/books/{isbn}", response_model=BookDTO)
def update_book(isbn: str, book: BookDTO):
try:
if book.isbn != isbn:
raise HTTPException(status_code=400, detail="O ISBN no corpo da solicitação não corresponde ao ISBN da URL.")
book_service.update_book(book)
return book
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao atualizar o livro: {e}")
# @router.put("/books/{isbn}"): Define uma rota PUT para atualizar um livro por seu ISBN.
# book: BookDTO: Recebe um objeto BookDTO no corpo da requisição.
# if book.isbn != isbn: Verifica se o ISBN no corpo corresponde ao ISBN na URL.
# book_service.update_book(book): Atualiza o livro usando o serviço.
# HTTPException(400): Lança erro 400 se os ISBNs não corresponderem.
@router.get("/v1/books/{isbn}", response_model=BookDTO)
def get_book(isbn: str):
book = book_service.get_book(isbn)
if not book:
raise HTTPException(status_code=404, detail="Livro não encontrado")
return book
# @router.get("/v1/books/{isbn}"): Define uma rota GET para obter um livro específico por ISBN.
# book_service.get_book(isbn): Obtém o livro do serviço.
# HTTPException(404): Lança erro 404 se o livro não for encontrado.
@router.post("/v1/books", response_model=BookDTO)
def add_book(book: BookDTO):
"""
This endpoint API, action add/update book
"""
try:
book_service.add_book(book)
return book
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao adicionar o livro: {e}")
# @router.post("/v1/books"): Define uma rota POST para adicionar um novo livro.
# book: BookDTO: Recebe o objeto BookDTO como entrada.
# book_service.add_book(book): Chama o método do serviço para adicionar o livro.
# HTTPException(500): Lança erro 500 se ocorrer algum problema ao adicionar o livro.
# Este código define um conjunto de rotas para a API de livros:
# GET /v1/books/query: Consulta livros por um filtro.
# GET /v1/books: Obtém todos os livros.
# DELETE /v1/books/{isbn}: Deleta um livro específico pelo ISBN.
# PUT /books/{isbn}: Atualiza um livro pelo ISBN.
# GET /v1/books/{isbn}: Obtém um livro específico pelo ISBN.
# POST /v1/books: Adiciona um novo livro.
# O serviço BookService é usado para gerenciar as operações,
# enquanto o objeto BookDTO define a estrutura dos dados do livro.
# As exceções são tratadas usando HTTPException para fornecer mensagens de erro específicas ao cliente.
- dto/: Data Transfer Objects, representados por
book_dto.py
, que definem a estrutura dos dados transferidos entre as camadas da aplicação.
# Este código define uma classe BookDTO que representa um Data Transfer Object (DTO) para um livro, usando o Pydantic, uma biblioteca Python usada para validação de dados e criação de esquemas. O BookDTO é utilizado para garantir que os dados do livro sejam estruturados de maneira consistente e para facilitar a validação e serialização dos dados quando se trabalha com APIs, como FastAPI.
from pydantic import BaseModel
# BaseModel: Importado da biblioteca Pydantic, a BaseModel serve como base para criar classes que representam objetos de dados. Essa classe fornece métodos e funcionalidades para validar automaticamente os dados atribuídos aos atributos.
class BookDTO(BaseModel):
isbn: str
tipo_livro: str
estante: str
idioma: str
titulo: str
autor: str
editora: str
ano: int
edicao: int
preco: float
peso: int
descricao: str
capa: str
# class BookDTO(BaseModel): Define a classe BookDTO que herda de BaseModel. Assim, essa classe pode validar dados automaticamente quando objetos são criados, garantindo que todos os campos possuam os tipos corretos.
# isbn: str: Representa o ISBN do livro, um identificador único para livros. É uma string.
# tipo_livro: str: Indica o tipo do livro (ex.: ficção, técnico, etc.). Também é uma string.
# estante: str: Representa a estante ou localização do livro na biblioteca. É uma string.
# idioma: str: Define o idioma do livro, como português ou inglês.
# titulo: str: Representa o título do livro.
# autor: str: Define o autor do livro.
# editora: str: Indica a editora do livro.
# ano: int: Representa o ano de publicação do livro. É um valor inteiro.
# edicao: int: Define a edição do livro, como "1ª edição", "2ª edição", etc.
# preco: float: Representa o preço do livro, como um valor de ponto flutuante (número decimal).
# peso: int: Define o peso do livro em gramas. É um valor inteiro.
#descricao: str: Representa uma descrição do livro, que pode incluir resumos ou outras informações úteis.
# capa: str: Define a URL da capa do livro ou uma referência à capa, podendo ser uma URL de imagem.
# Ao criar um objeto BookDTO, os dados passados são validados automaticamente.
# Caso algum campo esteja em um formato incorreto, o Pydantic levantará um erro, garantindo que os dados sejam consistentes e corretos antes de serem usados.
# Este código define um objeto de transferência de dados (BookDTO) para representar informações sobre um livro, usando a biblioteca Pydantic para garantir a validação e a consistência dos dados. Cada atributo da classe BookDTO representa uma característica do livro, como ISBN, título, autor, etc. Essa abordagem facilita a troca de informações no desenvolvimento de APIs, além de assegurar a qualidade e a consistência dos dados.
- repository/: Contém o
book_repository.py
, responsável pela interação direta com o Azure Table Storage, gerenciando as operações de persistência de dados.
# Este código define uma classe chamada BookRepository que é responsável por interagir com o serviço Azure Table Storage para realizar operações CRUD (Criar, Ler, Atualizar, Deletar) em uma tabela chamada BooksTable. A classe BookDTO é usada para representar os dados dos livros de forma estruturada.
from books.src.config.azure_config import AzureConfig
from azure.data.tables import TableServiceClient, TableClient, UpdateMode
from books.src.dto.book_dto import BookDTO
import json
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
# AzureConfig: Classe que gerencia a configuração dos serviços Azure, importada de outro módulo.
# TableServiceClient, TableClient, UpdateMode: Importados da Azure SDK para trabalhar com tabelas, permitindo manipular entidades e especificar o modo de atualização.
# BookDTO: Classe que define os atributos de um livro, usada para validar e organizar os dados.
# json: Biblioteca padrão do Python para serializar e desserializar dados JSON.
# ResourceExistsError, ResourceNotFoundError: Exceções da Azure SDK para tratar casos onde um recurso já existe ou não é encontrado.
class BookRepository:
# A classe BookRepository é responsável por implementar a lógica para manipular os livros na tabela BooksTable.
def __init__(self):
AzureConfig.create_table_if_not_exists("BooksTable")
self.table_client = AzureConfig.get_table_service_client().get_table_client("BooksTable")
# AzureConfig.create_table_if_not_exists("BooksTable"): Cria a tabela BooksTable se ela não existir.
# self.table_client: Obtém um cliente específico para interagir com a BooksTable.
def save_book(self, book_dto: BookDTO):
entity = {
'PartitionKey': 'Book',
'RowKey': book_dto.isbn,
'BookData': json.dumps(book_dto.dict()) # Salva o livro como JSON
}
insert_entity = self.table_client.upsert_entity(mode=UpdateMode.REPLACE, entity=entity)
print(f"Inserted entity: {insert_entity}")
# book_dto: BookDTO: Recebe um objeto BookDTO com os dados do livro.
# entity: Cria um dicionário representando uma entidade na tabela.
# PartitionKey e RowKey: São usados como identificadores no Azure Table Storage.
# BookData: Armazena os dados do livro em formato JSON.
# upsert_entity: Insere ou atualiza a entidade. O modo UpdateMode.REPLACE garante que, se o livro já existir, será substituído.
def get_book_by_isbn(self, isbn: str) -> BookDTO:
try:
entity = self.table_client.get_entity(partition_key='Book', row_key=isbn)
book_data = json.loads(entity['BookData'])
return BookDTO(**book_data)
except ResourceNotFoundError:
print(f"Livro com ISBN {isbn} não encontrado.")
return None
except Exception as e:
print(f"Erro ao buscar o livro: {e}")
return None
# get_entity: Obtém a entidade com base no PartitionKey e RowKey.
# json.loads(entity['BookData']): Desserializa os dados do livro.
# **return BookDTO(book_data): Constrói um objeto BookDTO com os dados obtidos.
# ResourceNotFoundError: Caso o livro não seja encontrado, imprime uma mensagem.
def query_books(self, filter_expression: str):
try:
entities = self.table_client.query_entities(filter=filter_expression)
books = [BookDTO(**json.loads(entity['BookData'])) for entity in entities]
return books
except Exception as e:
print(f"Erro ao realizar a consulta: {e}")
return []
# query_entities(filter=filter_expression): Realiza uma consulta na tabela com base na expressão de filtro fornecida.
# books: Constrói uma lista de objetos BookDTO a partir dos resultados.
def delete_book_by_isbn(self, isbn: str):
try:
self.table_client.delete_entity(partition_key='Book', row_key=isbn)
print(f"Livro com ISBN {isbn} deletado com sucesso.")
except ResourceNotFoundError:
print(f"Livro com ISBN {isbn} não encontrado para deletar.")
except Exception as e:
print(f"Erro ao deletar o livro: {e}")
# delete_entity: Deleta uma entidade na tabela com base em PartitionKey e RowKey.
# ResourceNotFoundError: Imprime uma mensagem se o livro não for encontrado.
def get_all_books(self):
try:
entities = self.table_client.list_entities()
books = [BookDTO(**json.loads(entity['BookData'])) for entity in entities]
return books
except Exception as e:
print(f"Erro ao buscar todos os livros: {e}")
return []
# list_entities(): Lista todas as entidades na tabela.
# books: Constrói uma lista de objetos BookDTO para cada entidade encontrada.
def update_book(self, book_dto: BookDTO):
# O método save_book já faz a atualização, pois usa upsert_entity
# self.save_book(book_dto)
# O método save_book já implementa a funcionalidade de inserção e atualização através do upsert_entity, portanto, o update_book apenas o reutiliza.
- services/: Implementa a lógica de negócios. O
book_service.py
realiza as operações principais, conectando o controlador aos repositórios.
from abc import ABC, abstractmethod
from books.src.dto.book_dto import BookDTO
# A classe IBookService define uma interface para o serviço de livros, com vários métodos abstratos que representam as operações que um serviço de gerenciamento de livros deve implementar.
class IBookService(ABC):
@abstractmethod
def process_queue_messages(self):
pass
# Métodos Abstratos
# @abstractmethod: Todos os métodos são decorados com @abstractmethod, o que significa que qualquer classe que herde de IBookService deve implementar esses métodos.
# process_queue_messages():
@abstractmethod
def get_book(self, isbn: str) -> BookDTO:
pass
# Método para processar mensagens em uma fila. Útil para operações assíncronas que gerenciam eventos relacionados a livros.
# get_book(isbn: str) -> BookDTO:
@abstractmethod
def query_books(self, filter_expression: str):
pass
# Método para obter um livro específico com base em seu ISBN.
# Deve retornar um objeto BookDTO.
# query_books(filter_expression: str):
@abstractmethod
def delete_book(self, isbn: str):
pass
# Método para realizar uma consulta nos livros com base em uma expressão de filtro.
# Retorna uma lista de livros que atendem ao critério definido.
# delete_book(isbn: str):
@abstractmethod
def get_all_books(self):
pass
# Método para obter todos os livros disponíveis no sistema.
# Pode retornar uma lista de objetos BookDTO. @abstractmethod
def add_book(self, book_dto: BookDTO):
pass
# Método para obter todos os livros disponíveis no sistema.
# Pode retornar uma lista de objetos BookDTO.
# add_book(book_dto: BookDTO):
@abstractmethod
def update_book(self, book_dto: BookDTO):
pass
# Método para adicionar um novo livro ao sistema.
# Recebe um objeto BookDTO que contém as informações do livro.
# update_book(book_dto: BookDTO):
# Método para atualizar as informações de um livro existente.
# Recebe um objeto BookDTO que contém os dados a serem atualizados.
# Resumo
# A classe IBookService é uma interface que define os métodos que qualquer serviço de gerenciamento de livros deve implementar. Usando a classe abstrata ABC e o decorador @abstractmethod, o código obriga as subclasses a fornecerem suas próprias implementações para todos os métodos abstratos.
# Isso é útil para garantir que todas as classes que implementam o serviço de livros sigam uma estrutura consistente, fornecendo um contrato que deve ser respeitado. Por exemplo, uma implementação específica do IBookService poderia usar uma base de dados SQL, enquanto outra poderia usar armazenamento em nuvem, mas ambas implementariam os mesmos métodos definidos aqui.
# Em termos de organização e boas práticas de desenvolvimento de software, isso promove a programação orientada a interfaces, facilitando a manutenção e escalabilidade do sistema, além de permitir o polimorfismo, onde diferentes implementações podem ser utilizadas de forma intercambiável.
import json
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from books.src.config.azure_config import AzureConfig
from books.src.dto.book_dto import BookDTO
from books.src.repository.book_repository import BookRepository
from books.src.services.interfaces.book_service_interface import IBookService
class BookService(IBookService):
def __init__(self):
self.queue_client = AzureConfig.get_queue_service_client().get_queue_client("livros-fila")
self.ensure_queue_exists()
self.book_repository = BookRepository()
def ensure_queue_exists(self):
try:
self.queue_client.create_queue()
print("Fila 'livros-fila' criada com sucesso.")
except HttpResponseError as e:
if "QueueAlreadyExists" in str(e):
print("A fila 'livros-fila' já existe.")
else:
print(f"Erro ao criar a fila 'livros-fila': {e}")
def process_queue_messages(self):
try:
messages = self.queue_client.receive_messages(max_messages=5)
if not messages:
print("Nenhuma mensagem encontrada na fila.")
for message in messages:
try:
# Convertendo a string para JSON
book_data_str = message.content.replace("'", '"')
book_data = json.loads(book_data_str)
# Converte o JSON para a classe BookDTO
book_dto = BookDTO(**book_data)
self.book_repository.save_book(book_dto)
self.queue_client.delete_message(message)
print(f"Livro salvo na tabela e mensagem excluída: {book_dto.titulo}")
except Exception as e:
print(f"Erro ao processar mensagem: {e}")
# Opcionalmente, você pode mover a mensagem para uma fila de mensagens com erro
except ResourceNotFoundError:
print("Erro: A fila 'livros-fila' não foi encontrada. Certifique-se de que a fila existe.")
except HttpResponseError as e:
print(f"Erro ao processar mensagem da fila: {e}")
def get_book(self, isbn: str) -> BookDTO:
return self.book_repository.get_book_by_isbn(isbn)
def query_books(self, filter_expression: str):
return self.book_repository.query_books(filter_expression)
def delete_book(self, isbn: str):
self.book_repository.delete_book_by_isbn(isbn)
def get_all_books(self):
return self.book_repository.get_all_books()
def add_book(self, book_dto: BookDTO):
self.book_repository.save_book(book_dto)
def update_book(self, book_dto: BookDTO):
self.book_repository.update_book(book_dto)
Este código define uma classe chamada BookService
que implementa a interface IBookService
. Essa classe gerencia várias operações relacionadas ao gerenciamento de livros, como salvar, obter, atualizar, excluir e processar mensagens em uma fila do Azure Storage. Vamos detalhar cada parte:
Importações
- json: Biblioteca padrão do Python usada para manipular dados no formato JSON.
- azure.core.exceptions: Importa exceções que podem ocorrer ao interagir com o Azure Storage, como
HttpResponseError
eResourceNotFoundError
. - AzureConfig: Configuração para acessar o Azure, obtendo o cliente de fila.
- BookDTO: Data Transfer Object que define a estrutura dos dados de um livro.
- BookRepository: Repositório que gerencia a persistência de dados no Azure Table Storage.
- IBookService: Interface que define os métodos que
BookService
deve implementar.
Classe BookService
A classe BookService
implementa a lógica de negócios do sistema de gerenciamento de livros. Ela interage com a fila do Azure Storage para processar mensagens e gerencia a persistência dos livros.
__init__
Método Construtor
- Inicializa um cliente para acessar a fila chamada
"livros-fila"
por meio da classe de configuraçãoAzureConfig
. - Chama o método
ensure_queue_exists
para garantir que a fila exista antes de qualquer operação. - Cria uma instância de
BookRepository
para acessar a camada de persistência.
Método ensure_queue_exists
- Garante que a fila
"livros-fila"
exista. - Caso a fila não exista, ele a cria e imprime uma mensagem de confirmação.
- Em caso de erro, verifica se o erro é porque a fila já existe. Caso contrário, imprime uma mensagem de erro.
Método process_queue_messages
- Recebe até cinco mensagens da fila (
max_messages=5
). - Se não houver mensagens, exibe uma mensagem indicando que a fila está vazia.
- Para cada mensagem recebida:
- Converte o conteúdo da mensagem de uma string para JSON, substituindo aspas simples por aspas duplas (para garantir compatibilidade JSON).
- Converte o JSON para um objeto da classe
BookDTO
. - Salva o livro na tabela usando
book_repository
. - Exclui a mensagem da fila depois de processá-la com sucesso.
- Em caso de falha, imprime um erro e sugere mover a mensagem para uma fila de mensagens com erro.
Método get_book
- Recebe um
isbn
como argumento. - Usa
book_repository
para obter o livro correspondente ao ISBN.
Método query_books
- Permite buscar livros usando uma expressão de filtro, repassando a solicitação para o repositório.
Método delete_book
- Recebe um
isbn
e exclui o livro correspondente usando obook_repository
.
Método get_all_books
- Retorna todos os livros disponíveis usando o repositório.
Método add_book
- Recebe um objeto
BookDTO
e salva o livro na tabela usandobook_repository
.
Método update_book
- Recebe um objeto
BookDTO
e atualiza os dados do livro usando obook_repository
.
docker-compose.yaml
Este arquivo define os serviços que serão executados pela aplicação. Inclui a configuração do Azurite (emulador do Azure Storage), a API books
, a API books_create
e o books_worker
para processamento das mensagens da fila. O Docker Compose facilita a inicialização e integração de todos esses serviços em um ambiente único.
requirements.txt
Arquivo que lista todas as dependências do projeto. Este arquivo é usado para instalar todas as bibliotecas necessárias para rodar a aplicação e garantir que todos os desenvolvedores estejam utilizando as mesmas versões.
Tecnologias Utilizadas
- Python 3.11: Para desenvolver a lógica do sistema e as APIs.
- FastAPI: Framework web para criação rápida de APIs.
- Azurite: Ferramenta para emular os serviços do Azure Storage.
- Docker Compose: Para orquestrar todos os serviços do projeto.
Configuração e Execução do Projeto
Clonando o Repositório
Para iniciar, clone o repositório do projeto usando o comando abaixo:
git clone https://github.com/andersonluizpereira/microsoft_samples.git
cd microsoft_samples
Configuração do Docker Compose
O arquivo docker-compose.yaml
está configurado para executar os seguintes serviços:
- Azurite: Emulador do Azure Storage.
- books_api: API para gerenciar livros.
- books_queue: API para enviar livros para a fila de mensagens.
- books_worker: Worker para processar mensagens da fila e salvar os dados.
- photo: Blob Storage para enviar e buscar arquivos.
Para executar o projeto, utilize o comando:
docker-compose up --build -d
Este comando irá construir as imagens e iniciar os serviços em segundo plano.
Dependências
As dependências do projeto são instaladas automaticamente pelo Docker Compose, mas também podem ser instaladas manualmente:
pip install -r requirements.txt
Endpoints das APIs
Books API (books/main.py
)
GET /api/v1/books/{isbn}
: Retorna os detalhes de um livro específico pelo ISBN.GET /api/v1/books/query
: Busca livros com base em uma expressão de filtro.GET /api/v1/books
: Retorna todos os livros cadastrados.POST /api/v1/books
: Cadastra um novo livro.PUT /api/v1/books/{isbn}
: Atualiza um livro existente.DELETE /api/v1/books/{isbn}
: Deleta um livro pelo ISBN.
Desenvolvendo a API de Blob com FastAPI
1. Instalando Dependências
Crie e ative um ambiente virtual (opcional, mas recomendado):
python -m venv venv
source venv/bin/activate # Para Linux/Mac
# Ou
venv\Scripts\activate # Para Windows
Instale as dependências necessárias usando pip
:
pip install fastapi uvicorn python-multipart azure-storage-blob
Descrição das Dependências:
fastapi
: Framework web para construir APIs rápidas e eficientes.uvicorn
: Servidor ASGI para rodar a aplicação FastAPI.python-multipart
: Necessário para lidar com uploads de arquivos.azure-storage-blob
: Biblioteca oficial da Microsoft para interagir com o Azure Blob Storage.
2. Estrutura do Projeto
Crie um arquivo chamado main.py
e insira o seguinte código:
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse, Response
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
import os
import logging
app = FastAPI()
# Configura o logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configurações do Azurite
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
CONTAINER_NAME = "fotos"
def initialize_blob_service():
"""Inicializa o serviço Blob e o container."""
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(CONTAINER_NAME)
try:
container_client.create_container()
logger.info(f"Container '{CONTAINER_NAME}' criado.")
except Exception as e:
logger.info(f"Container '{CONTAINER_NAME}' já existe ou ocorreu um erro: {e}")
return blob_service_client
except Exception as e:
logger.error(f"Erro ao conectar ao Azurite: {e}")
raise e
# Inicializa o BlobServiceClient
blob_service_client = initialize_blob_service()
@app.post("/upload")
async def upload_photo(file: UploadFile = File(...)):
"""
Endpoint para fazer upload de uma foto.
"""
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Arquivo enviado não é uma imagem.")
blob_name = file.filename
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
# Lê o conteúdo do arquivo
file_content = await file.read()
blob_client.upload_blob(file_content, overwrite=True)
logger.info(f"Foto '{blob_name}' enviada com sucesso.")
return JSONResponse(status_code=200, content={"message": f"Foto '{blob_name}' enviada com sucesso."})
except Exception as e:
logger.error(f"Erro ao enviar a foto: {e}")
raise HTTPException(status_code=500, detail=f"Erro ao enviar a foto: {e}")
@app.get("/photos/{blob_name}")
async def get_photo(blob_name: str):
"""
Endpoint para obter informações sobre uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
logger.info(f"Informações da foto '{blob_name}' recuperadas com sucesso.")
return JSONResponse(status_code=200, content={"blob_name": blob_name, "size": blob.size})
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
@app.get("/download/{blob_name}")
async def download_photo(blob_name: str):
"""
Endpoint para baixar uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
content = blob.readall()
# Detecta o tipo de conteúdo com base na extensão do arquivo
if blob_name.lower().endswith('.png'):
media_type = "image/png"
elif blob_name.lower().endswith('.gif'):
media_type = "image/gif"
else:
media_type = "image/jpeg"
logger.info(f"Foto '{blob_name}' baixada com sucesso.")
return Response(content, media_type=media_type)
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada para download: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
# Função main opcional para rodar com Uvicorn
def main():
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
# Executa main() se o script for executado diretamente
if __name__ == "__main__":
main()
3. Explicação Detalhada do Código
3.1. Importações e Configurações Iniciais
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse, Response
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
import os
import logging
app = FastAPI()
# Configura o logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
- FastAPI e FastAPI Responses: Utilizados para criar os endpoints e enviar respostas JSON ou binárias.
- Azure Storage Blob: Bibliotecas necessárias para interagir com o Azure Blob Storage.
- Logging: Configurado para registrar informações e erros, facilitando a depuração.
3.2. Configurações do Azurite
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
CONTAINER_NAME = "fotos"
- AZURITE_CONNECTION_STRING: String de conexão padrão para Azurite. Assegure-se de que está correta e não contém espaços ou quebras de linha indesejadas.
- CONTAINER_NAME: Nome do container onde as fotos serão armazenadas. Neste caso, “fotos”.
3.3. Inicialização do BlobServiceClient
def initialize_blob_service():
"""Inicializa o serviço Blob e o container."""
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(CONTAINER_NAME)
try:
container_client.create_container()
logger.info(f"Container '{CONTAINER_NAME}' criado.")
except Exception as e:
logger.info(f"Container '{CONTAINER_NAME}' já existe ou ocorreu um erro: {e}")
return blob_service_client
except Exception as e:
logger.error(f"Erro ao conectar ao Azurite: {e}")
raise e
# Inicializa o BlobServiceClient
blob_service_client = initialize_blob_service()
- BlobServiceClient: Cliente principal para interagir com o serviço Blob.
- ContainerClient: Cliente para interagir com um container específico.
- Criação do Container: Tenta criar o container; se já existir, captura a exceção e continua.
3.4. Endpoint /upload
para Upload de Fotos
@app.post("/upload")
async def upload_photo(file: UploadFile = File(...)):
"""
Endpoint para fazer upload de uma foto.
"""
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Arquivo enviado não é uma imagem.")
blob_name = file.filename
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
# Lê o conteúdo do arquivo
file_content = await file.read()
blob_client.upload_blob(file_content, overwrite=True)
logger.info(f"Foto '{blob_name}' enviada com sucesso.")
return JSONResponse(status_code=200, content={"message": f"Foto '{blob_name}' enviada com sucesso."})
except Exception as e:
logger.error(f"Erro ao enviar a foto: {e}")
raise HTTPException(status_code=500, detail=f"Erro ao enviar a foto: {e}")
- Validação do Tipo de Arquivo: Garante que apenas arquivos de imagem sejam enviados.
- BlobClient: Cliente para interagir com um blob específico dentro do container.
- Upload do Blob: Lê o conteúdo do arquivo e faz o upload para o Azure Blob Storage. O parâmetro
overwrite=True
permite sobrescrever blobs existentes com o mesmo nome. - Resposta JSON: Retorna uma mensagem de sucesso ou um erro apropriado.
3.5. Endpoint /photos/{blob_name}
para Obter Informações da Foto
@app.get("/photos/{blob_name}")
async def get_photo(blob_name: str):
"""
Endpoint para obter informações sobre uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
logger.info(f"Informações da foto '{blob_name}' recuperadas com sucesso.")
return JSONResponse(status_code=200, content={"blob_name": blob_name, "size": blob.size})
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
- Download do Blob: Recupera o blob para obter suas propriedades, como tamanho.
- Resposta JSON: Retorna informações relevantes sobre o blob ou um erro caso não seja encontrado.
3.6. Endpoint /download/{blob_name}
para Baixar a Foto
@app.get("/download/{blob_name}")
async def download_photo(blob_name: str):
"""
Endpoint para baixar uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
content = blob.readall()
# Detecta o tipo de conteúdo com base na extensão do arquivo
if blob_name.lower().endswith('.png'):
media_type = "image/png"
elif blob_name.lower().endswith('.gif'):
media_type = "image/gif"
else:
media_type = "image/jpeg"
logger.info(f"Foto '{blob_name}' baixada com sucesso.")
return Response(content, media_type=media_type)
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada para download: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
- Download do Conteúdo do Blob: Lê o conteúdo binário do blob.
- Detecção do Tipo de Conteúdo: Baseado na extensão do arquivo, define o tipo MIME apropriado para a resposta.
- Resposta Binária: Retorna a foto com o tipo de mídia correto ou um erro caso não seja encontrada.
3.7. Função main()
para Rodar o Servidor
def main():
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
# Executa main() se o script for executado diretamente
if __name__ == "__main__":
main()
- Uvicorn: Serve a aplicação FastAPI.
- Parâmetros:
"main:app"
: Refere-se ao arquivomain.py
e à instânciaapp
da FastAPI.host
: Endereço IP onde o servidor será executado.port
: Porta onde o servidor escutará as requisições.reload=True
: Permite recarregar automaticamente o servidor quando mudanças no código são detectadas (útil durante o desenvolvimento).
4. Executando a Aplicação
Com o Azurite em execução e as dependências instaladas, inicie o servidor FastAPI executando:
python main.py
Ou, alternativamente, usando o Uvicorn diretamente:
uvicorn main:app --reload
O servidor será iniciado em http://127.0.0.1:8000
. Você pode acessar o Swagger UI para interagir com a API de forma visual em http://127.0.0.1:8000/docs
.
Interagindo com a API usando cURL
Para testar os endpoints da API, você pode usar comandos curl
no terminal. A seguir, apresentamos exemplos para cada um dos endpoints desenvolvidos.
1. Upload de Foto (POST /upload
)
Descrição: Envia uma foto para o container fotos
no Azure Blob Storage.
Comando cURL:
curl -X POST "http://127.0.0.1:8000/upload" \
-H "Content-Type: multipart/form-data" \
-F "file=@/caminho/para/sua/foto.jpg"
Explicação dos Parâmetros:
-X POST
: Especifica que a requisição é do tipo POST."http://127.0.0.1:8000/upload"
: URL do endpoint de upload.-H "Content-Type: multipart/form-data"
: Define o tipo de conteúdo como multipart/form-data, necessário para upload de arquivos.-F "file=@/caminho/para/sua/foto.jpg"
: Anexa o arquivo que será enviado. Substitua"/caminho/para/sua/foto.jpg"
pelo caminho real do arquivo que deseja fazer upload.
Exemplo Prático:
Se você tem uma foto chamada imagem.jpg
no diretório atual:
curl -X POST "http://127.0.0.1:8000/upload" \
-H "Content-Type: multipart/form-data" \
-F "file=@./imagem.jpg"
Resposta Esperada:
{
"message": "Foto 'imagem.jpg' enviada com sucesso."
}
2. Obter Informações da Foto (GET /photos/{blob_name}
)
Descrição: Recupera informações sobre uma foto específica armazenada no container fotos
.
Comando cURL:
curl -X GET "http://127.0.0.1:8000/photos/imagem.jpg"
Explicação:
- Substitua
imagem.jpg
pelo nome real da foto que você enviou.
Exemplo Prático:
curl -X GET "http://127.0.0.1:8000/photos/imagem.jpg"
Resposta Esperada:
{
"blob_name": "imagem.jpg",
"size": 102400
}
Onde size
representa o tamanho do arquivo em bytes.
3. Baixar a Foto (GET /download/{blob_name}
)
Descrição: Baixa uma foto específica armazenada no container fotos
.
Comando cURL:
curl -X GET "http://127.0.0.1:8000/download/imagem.jpg" --output imagem_baixada.jpg
Resposta Esperada:
A foto será baixada e salva como imagem_baixada.jpg
no diretório atual.
Solução de Problemas Comuns
Durante o desenvolvimento, é comum encontrar erros ou comportamentos inesperados. A seguir, listamos alguns problemas comuns e como resolvê-los.
1. Erro de Autenticação
Erro Recebido:
{
"detail": "Erro ao enviar a foto: Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.\nRequestId:c1be2774-598e-455c-984f-6ceb73d00274\nTime:2024-11-02T12:19:02.237Z\nErrorCode:AuthorizationFailure\nContent: <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Error>\n <Code>AuthorizationFailure</Code>\n <Message>Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.\nRequestId:c1be2774-598e-455c-984f-6ceb73d00274\nTime:2024-11-02T12:19:02.237Z</Message>\n</Error>"
}
Solução:
Esse erro indica que o cabeçalho de Authorization está incorretamente formado ou que a assinatura está errada. Siga estes passos para resolver:
- Verifique a String de Conexão:
- Assegure-se de que a
AZURITE_CONNECTION_STRING
está correta e sem espaços extras ou quebras de linha. A string de conexão padrão para Azurite é:
DefaultEndpointsProtocol=http;
AccountName=devstoreaccount1;
AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;
BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;
Exemplo Correto em Python:
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
Certifique-se de que o Azurite Está em Execução:
Verifique se o Azurite está rodando e ouvindo na porta correta (10000
para Blob Storage). Se não estiver, execute novamente:
azurite --silent --location ./azurite --debug ./azurite/debug.log
- Verifique as Credenciais:
- As credenciais padrão para Azurite são:
- Account Name:
devstoreaccount1
- Account Key:
Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
- Atualize a Biblioteca
azure-storage-blob
: - Certifique-se de que você está usando a versão mais recente da biblioteca:
pip install --upgrade azure-storage-blob
2. Verifique os Logs
Adicione logs detalhados para facilitar a identificação de onde o problema está ocorrendo. No código fornecido, já utilizamos o módulo logging
para registrar informações e erros. Verifique os logs no console onde o FastAPI está rodando e no arquivo de log do Azurite (./azurite/debug.log
).
3. Teste com um Script Simples
Antes de integrar tudo na API, teste a conexão com Azurite usando um script Python simples:
from azure.storage.blob import BlobServiceClient
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
def test_connection():
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
containers = blob_service_client.list_containers()
print("Containers existentes:")
for container in containers:
print(f"- {container['name']}")
except Exception as e:
print(f"Erro ao conectar ou listar containers: {e}")
if __name__ == "__main__":
test_connection()
Execute o Script:
python test_connection.py
Objetivo:
- Verificar se a conexão com Azurite está funcionando.
- Listar os containers existentes para confirmar se o container
fotos
está presente.
4. Verifique o Relógio do Sistema
Embora esteja usando Azurite localmente, é importante garantir que o relógio do sistema esteja sincronizado. Diferenças significativas no tempo podem causar falhas de autenticação.
5. Permissões e Firewall
Certifique-se de que nenhuma configuração de firewall ou segurança está bloqueando as portas usadas pelo Azurite (10000
) e pelo FastAPI (8000
).
Azure Queue na prática
API para enviar livros para a fila de mensagens.
No post acima eu comentei sobre em como criar um crud com Azure Table e Azure Queue.
Aqui vou envinar na prática como envia um dado para fila através de uma API rest, com FastApi Python.
Estrutura do Projeto
A estrutura do projeto é organizada da seguinte forma:
- Books Create API: API FastAPI que envia os livros para uma fila de mensagens.
- Azurite: Emulador do Azure Storage para realizar testes localmente.
├── books_create
│ ├── main.py
│ └── src
│ ├── config
│ │ └── azure_config.py
│ ├── controller
│ │ └── book_controller.py
│ ├── dto
│ │ └── book_dto.py
│ └── services
│ └── book_service.py
├── docker-compose.yaml
├── requirements.txt
Vamos para prática
books_create/
Este diretório contém a API para envio dos livros para a fila. Ele inclui:
- main.py: Ponto de entrada da API FastAPI que lida com o envio de livros para a fila.
# Este código define uma aplicação FastAPI e registra um roteador que provavelmente lida com operações relacionadas a livros. Ao final, ele roda o servidor usando o uvicorn, que é um servidor ASGI eficiente, ideal para executar aplicações FastAPI.
from fastapi import FastAPI
from books_create.src.controller.book_controller import router as book_created_router
import uvicorn
# FastAPI: Importado da biblioteca FastAPI, usado para criar e definir uma API web.
# router as book_created_router: Importa um roteador chamado router do módulo book_controller, mas é renomeado localmente como book_created_router. Esse roteador provavelmente contém definições de rotas para lidar com operações relacionadas aos livros.
# uvicorn: Importado para executar a aplicação FastAPI como servidor.
app = FastAPI()
# app = FastAPI(): Cria uma instância da aplicação FastAPI. Essa instância é usada para definir e registrar todas as rotas e configurações da aplicação.
app.include_router(book_created_router, prefix="/books", tags=["books"])
# app.include_router(): Registra um roteador na aplicação FastAPI.
# book_created_router: O roteador importado do book_controller é incluído.
# prefix="/books": Define um prefixo para todas as rotas registradas por este roteador. Isso significa que qualquer rota definida no book_created_router estará sob o prefixo /books.
# tags=["books"]: Define uma tag para agrupar as rotas na documentação automática da API. Isso ajuda a organizar as rotas, especialmente na documentação gerada automaticamente pelo FastAPI (ex.: /docs).
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
# if name == "main": Esse bloco de código é executado apenas se o arquivo for executado diretamente, e não quando for importado como um módulo.
# uvicorn.run(): Executa o servidor usando uvicorn.
# app: Especifica que a instância da aplicação FastAPI (app) deve ser executada.
# host="0.0.0.0": Faz com que o servidor esteja acessível em qualquer interface de rede. Isso permite que a aplicação seja acessada de fora do host local.
# port=8000: Define a porta em que o servidor vai rodar. Neste caso, a aplicação estará acessível na porta 8000.
- config/: Inclui
azure_config.py
para configurar a conexão com o Azure.
# Este código define uma classe chamada AzureConfig que fornece um método para obter um cliente do serviço de filas (Queue Service Client) da Azure. Ele usa a biblioteca da Azure Storage SDK para conectar e interagir com o Azure Queue Storage, que é um serviço de fila utilizado para comunicação assíncrona entre componentes distribuídos de uma aplicação.
import os
from azure.storage.queue import QueueServiceClient
# os: Biblioteca padrão do Python, usada aqui para obter variáveis de ambiente do sistema operacional.
# QueueServiceClient: Classe da biblioteca Azure Storage SDK usada para se conectar e gerenciar filas de armazenamento da Azure.
class AzureConfig:
# class AzureConfig: Define uma classe para organizar a configuração dos serviços da Azure. Nesse caso, a classe tem como foco a configuração do cliente de filas.
@staticmethod
def get_queue_service_client():
# String de conexão para o emulador local
connect_str = os.getenv("AZURE_STORAGE_QUEUE_CONNECTION_STRING")
return QueueServiceClient.from_connection_string(connect_str)
# @staticmethod: Esse decorador indica que o método pode ser chamado diretamente na classe, sem necessidade de criar uma instância dela. É útil porque get_queue_service_client() não precisa acessar nenhum atributo da classe.
# get_queue_service_client(): Método que retorna um cliente do serviço de filas.
# connect_str = os.getenv("AZURE_STORAGE_QUEUE_CONNECTION_STRING"): Obtém a string de conexão do serviço de filas da Azure a partir de uma variável de ambiente chamada "AZURE_STORAGE_QUEUE_CONNECTION_STRING". Essa string de conexão contém as informações necessárias para autenticar e se conectar ao serviço.
# QueueServiceClient.from_connection_string(connect_str): Cria e retorna uma instância do QueueServiceClient usando a string de conexão obtida. Essa instância permite interagir com o Azure Queue Storage para criar, listar, deletar filas, entre outras operações.
# Este código:
# Define uma classe chamada AzureConfig que fornece um método estático para obter um cliente do Azure Queue Storage.
# A configuração é baseada em uma string de conexão que deve ser armazenada em uma variável de ambiente ("AZURE_STORAGE_QUEUE_CONNECTION_STRING").
# O método get_queue_service_client() cria uma instância do QueueServiceClient usando a string de conexão fornecida e a retorna, possibilitando que você se conecte ao serviço de filas da Azure para realizar operações.
# Esse tipo de configuração é importante para permitir a reutilização e a centralização das conexões aos serviços da Azure, além de garantir que credenciais e configurações sensíveis sejam gerenciadas de maneira segura (usando variáveis de ambiente ao invés de serem codificadas diretamente no código).
- controller/: Define os endpoints para enviar os livros para a fila, através do
book_controller.py
.
# Este código define uma rota da API usando FastAPI para adicionar um livro, enviando os dados do livro para um serviço de fila. Ele faz isso usando um APIRouter para organizar as rotas e definir operações relacionadas ao gerenciamento de livros.
from fastapi import APIRouter, HTTPException
from books_create.src.dto.book_dto import BookDTO
from books_create.src.services.book_service import BookService
# APIRouter: Importado do FastAPI, usado para criar um roteador que permite agrupar e organizar as rotas da API de forma modular.
# HTTPException: Usado para retornar exceções HTTP personalizadas em resposta a situações de erro.
# BookDTO: Importado do módulo dto, representa o Data Transfer Object (DTO) do livro, que define a estrutura e os tipos de dados esperados para um livro.
# BookService: Importado do módulo services, é um serviço que contém a lógica de negócios relacionada à manipulação de livros.
router = APIRouter()
book_service = BookService()
# router = APIRouter(): Cria uma instância do roteador para gerenciar as rotas relacionadas aos livros.
# book_service = BookService(): Cria uma instância do BookService, que contém os métodos para realizar operações no contexto dos livros.
@router.post("/add-book/", status_code=201)
async def add_book(book_dto: BookDTO):
try:
book = book_service.add_book(book_dto.dict())
return {"message": "Livro enviado para a fila com sucesso", "book": book}
except RuntimeError as e:
raise HTTPException(status_code=500, detail=str(e))
# @router.post("/add-book/", status_code=201): Define uma rota POST para o endpoint /add-book/.
# status_code=201: Define o código de status 201 (Created), que indica que um recurso foi criado com sucesso.
# # async def add_book(book_dto: BookDTO): Define uma função assíncrona chamada add_book que recebe como parâmetro um objeto do tipo BookDTO.
# book_dto: BookDTO: book_dto é do tipo BookDTO, o que significa que os dados recebidos devem estar no formato esperado pelo DTO. Isso facilita a validação dos dados.
# book_service.add_book(book_dto.dict()): Converte o objeto BookDTO em um dicionário (usando .dict()) e passa para o método add_book do BookService.
# Isso indica que os dados do livro serão adicionados (provavelmente enviados para uma fila ou armazenados).
# return {"message": "Livro enviado para a fila com sucesso", "book": book}: Retorna uma mensagem de sucesso e os dados do livro após o serviço ser executado.
# except RuntimeError as e: Se ocorrer um erro do tipo RuntimeError durante a execução do serviço:
# raise HTTPException(status_code=500, detail=str(e)): Levanta uma exceção HTTP com o código de status 500 (Internal Server Error), incluindo a mensagem do erro para ajudar no diagnóstico do problema.
- dto/: Define a estrutura dos dados enviados para a fila em
book_dto.py
.
# Este código define uma classe chamada BookDTO que herda de BaseModel (provavelmente da biblioteca Pydantic). DTO significa Data Transfer Object, que é um padrão de design usado para transportar dados entre processos ou camadas de uma aplicação. A classe BookDTO é utilizada para definir a estrutura dos dados de um livro, incluindo a validação dos tipos de dados.
# Importação Provável
# Embora não esteja explicitamente mostrado no código, a classe BaseModel faz parte da biblioteca Pydantic, que é usada no Python para criar classes que validam os dados de entrada automaticamente, garantindo que os dados sejam válidos antes de serem usados. Então, o código deveria começar com:
class BookDTO(BaseModel):
isbn: str
tipo_livro: str
estante: str
idioma: str
titulo: str
autor: str
editora: str
ano: int
edicao: int
preco: float
peso: int
descricao: str
capa: str
# Atributos da Classe BookDTO
# isbn: str: Representa o ISBN do livro, que é um identificador único internacionalmente reconhecido para livros.
# tipo_livro: str: Indica o tipo do livro (ex.: "Ficção", "Técnico", "Didático", etc.).
# estante: str: Refere-se à localização do livro, como a estante onde ele está armazenado (útil em uma biblioteca).
# idioma: str: Define o idioma do livro, como "Português", "Inglês", etc.
# titulo: str: Representa o título do livro.
# autor: str: Indica o autor do livro.
# editora: str: Refere-se à editora responsável pela publicação do livro.
# ano: int: Representa o ano de publicação do livro. É um valor do tipo int (número inteiro).
# edicao: int: Indica a edição do livro, por exemplo, "1ª edição", "2ª edição".
# preco: float: Representa o preço do livro em moeda (valor decimal).
# peso: int: Define o peso do livro, geralmente em gramas.
# descricao: str: Fornece uma descrição do livro, que pode ser um resumo ou outras informações relevantes.
# capa: str: Pode representar a URL de uma imagem da capa do livro ou uma referência à capa.
# Uso do BookDTO
# Validação dos Dados: Como BookDTO herda de BaseModel, os dados são automaticamente validados quando uma instância é criada. Por exemplo, se ano receber um valor que não seja um número inteiro, o Pydantic lançará um erro de validação.
# Conversão: A classe BaseModel também possui métodos que facilitam a conversão dos dados para JSON ou para dicionário, o que é útil em APIs REST para serialização e deserialização dos dados.
# Simplicidade e Reuso: O uso de DTOs ajuda a simplificar a lógica ao garantir que todos os dados têm a estrutura correta antes de serem processados por outros componentes.
- services/: Implementa a lógica necessária para enviar os livros para a fila de mensagens.
# Este código define uma interface chamada BookServiceInterface usando a biblioteca abc (Abstract Base Class). A interface é uma classe abstrata que fornece um contrato para qualquer classe que a implemente, definindo os métodos que devem ser implementados. Neste caso, o método add_book é definido como abstrato e precisa ser implementado por qualquer classe que herde de BookServiceInterface.
from abc import ABC, abstractmethod
# ABC: Importado da biblioteca abc (Abstract Base Classes), é usado para definir classes abstratas. Uma classe abstrata é uma classe que não pode ser instanciada diretamente e é usada como base para outras classes.
# abstractmethod: Importado da biblioteca abc, é um decorador que marca um método como abstrato. Isso significa que o método deve ser implementado por qualquer subclasse que herde da classe abstrata.
class BookServiceInterface(ABC):
@abstractmethod
def add_book(self, book_data: dict):
pass
# class BookServiceInterface(ABC): Define uma classe chamada BookServiceInterface que herda de ABC.
# Ao herdar de ABC, a classe se torna uma classe abstrata, ou seja, não pode ser instanciada diretamente e serve como um contrato para outras classes.
# @abstractmethod: Marca o método que segue como um método abstrato. Isso significa que qualquer classe que herde de BookServiceInterface deve fornecer uma implementação para esse método.
# def add_book(self, book_data: dict): Define um método chamado add_book, que é um método abstrato.
# book_data: dict: O método recebe um argumento chamado book_data, que é um dicionário (dict). Esse dicionário deve conter os dados do livro a ser adicionado.
# #pass: O método não possui implementação aqui, pois ele é apenas uma definição que deve ser implementada por uma subclasse.
# O código define uma interface (BookServiceInterface) que contém um método abstrato chamado add_book. A intenção é que essa interface seja herdada por outras classes que precisarão implementar esse método para adicionar um livro, garantindo que elas sigam um contrato específico.
# Propósito de uma Interface/Classe Abstrata
# Contrato de Implementação: Ao usar uma classe abstrata e métodos abstratos, você força todas as classes que herdem de BookServiceInterface a implementar o método add_book. Isso garante que qualquer serviço que implemente essa interface terá um método para adicionar um livro.
# Organização e Padronização: Ajuda a manter uma estrutura padronizada e facilita o entendimento do código, pois qualquer classe que implemente BookServiceInterface deve ter o comportamento especificado.
# Polimorfismo: Com uma interface como BookServiceInterface, você pode trabalhar com diferentes implementações de BookServiceInterface de maneira intercambiável, o que é um princípio fundamental da programação orientada a objetos.
# Este código implementa a classe BookService, que herda da interface BookServiceInterface. A classe BookService é responsável por enviar informações de livros para uma fila no Azure Queue Storage. Ele usa a Azure Storage SDK para gerenciar a comunicação com a fila e inclui lógica para garantir que livros não sejam duplicados na fila.
from books_create.src.config.azure_config import AzureConfig
from azure.core.exceptions import ResourceExistsError, HttpResponseError
from books_create.src.services.interfaces.book_service_interface import BookServiceInterface
# AzureConfig: Classe que gerencia as configurações para conectar-se aos serviços Azure.
# ResourceExistsError: Exceção lançada quando se tenta criar um recurso que já existe.
# HttpResponseError: Exceção lançada quando há uma falha na resposta HTTP ao tentar interagir com o Azure.
# BookServiceInterface: Interface que define os métodos que BookService precisa implementar. Aqui, a classe BookService está implementando essa interface.
class BookService(BookServiceInterface):
# A classe BookService é responsável por enviar os dados do livro para uma fila do Azure.
def __init__(self):
self.queue_client = AzureConfig.get_queue_service_client().get_queue_client("livros-fila")
self.sent_books = set() # Armazenamento em memória para controlar os livros já enviados
try:
self.queue_client.create_queue()
except ResourceExistsError:
pass # A fila já existe, então ignoramos o erro
# self.queue_client:
# AzureConfig.get_queue_service_client() obtém o cliente do serviço de filas.
# .get_queue_client("livros-fila") cria um cliente específico para a fila chamada "livros-fila".
# self.sent_books = set():
# Cria um conjunto (set) para armazenar os ISBNs dos livros que já foram enviados para a fila.
# Este conjunto é usado para evitar o envio duplicado de livros.
# try...except:
# self.queue_client.create_queue(): Tenta criar a fila chamada "livros-fila".
# except ResourceExistsError: Caso a fila já exista, esse erro será lançado. Ele é tratado aqui para ignorar a criação se a fila já estiver disponível.
def add_book(self, book_data: dict):
try:
# Verificar se o livro já foi enviado
if book_data['isbn'] in self.sent_books:
raise RuntimeError("Este livro já foi enviado para a fila anteriormente.")
self.queue_client.send_message(book_data)
self.sent_books.add(book_data['isbn']) # Registrar que o livro foi enviado
print(f"Mensagem enviada: {book_data}")
except HttpResponseError as e:
raise RuntimeError(f"Erro ao enviar mensagem para a fila: {e}")
return book_data
## add_book(self, book_data: dict):
# Método responsável por enviar um livro para a fila.
# book_data é um dicionário contendo as informações do livro (incluindo o ISBN).
# Verificar se o livro já foi enviado:
# if book_data['isbn'] in self.sent_books: Verifica se o ISBN do livro já está no conjunto sent_books. Se sim, lança um erro (RuntimeError) para evitar envio duplicado.
# Enviar o livro para a fila:
# self.queue_client.send_message(book_data): Envia a mensagem (dados do livro) para a fila.
# self.sent_books.add(book_data['isbn']): Adiciona o ISBN ao conjunto sent_books para registrar que o livro já foi enviado.
# print(f"Mensagem enviada: {book_data}"): Imprime uma mensagem de confirmação de que o livro foi enviado para a fila.
# Tratamento de Erros:
# except HttpResponseError as e: Captura um erro que possa ocorrer durante o envio da mensagem para a fila.
# raise RuntimeError(f"Erro ao enviar mensagem para a fila: {e}"): Lança uma nova exceção com uma mensagem mais clara para indicar que houve um problema ao enviar os dados.
# Retorno:
# return book_data: Retorna os dados do livro depois de serem enviados com sucesso.
Books Create API (books_create/main.py
)
POST /books
: Envia um livro para a fila de mensagens. Observação é o mesmo main.py de cima.
Construindo uma API de Upload e Download de Fotos com FastAPI e Azure Blob Storage usando Azurite
No cenário atual de desenvolvimento de aplicações web, o armazenamento eficiente e seguro de arquivos é fundamental. O Azure Blob Storage, serviço de armazenamento de objetos da Microsoft, oferece uma solução robusta para armazenar grandes quantidades de dados não estruturados, como imagens, vídeos, documentos e backups. Para desenvolvedores que desejam testar e desenvolver localmente antes de implantar na nuvem, o Azurite serve como um emulador local do Azure Blob Storage, proporcionando um ambiente de desenvolvimento semelhante ao ambiente de produção sem custos adicionais.
Neste artigo, vamos explorar como construir uma API robusta utilizando FastAPI, um framework web moderno e rápido para Python, que interage com o Azure Blob Storage via Azurite. Abordaremos desde a configuração do ambiente, passando pelo desenvolvimento da API, até a utilização de comandos curl
para interagir com os endpoints da API.
Prerequisitos
Antes de começarmos, certifique-se de ter o seguinte instalado e configurado em sua máquina:
- Python 3.7+: Download e Instalação
- Node.js e npm: Necessários para instalar o Azurite. Download e Instalação
- FastAPI e Uvicorn: Serão instalados posteriormente.
- Azurite: Emulador local do Azure Blob Storage.
- cURL: Ferramenta de linha de comando para transferir dados com URLs. Geralmente já está instalada em sistemas Unix; para Windows, pode ser baixada aqui.
Configurando o Azurite
1. Instalação do Azurite
Azurite pode ser instalado globalmente usando o npm. Abra seu terminal e execute:
npm install -g azurite
2. Iniciando o Azurite
Após a instalação, inicie o Azurite em um terminal separado para que ele permaneça em execução enquanto desenvolve sua aplicação:
azurite --silent --location ./azurite --debug ./azurite/debug.log
Parâmetros Explicados:
--silent
: Executa o Azurite sem logs no console.--location
: Define o diretório onde o Azurite armazenará os dados.--debug
: Define o caminho para o arquivo de log de depuração.
Por padrão, Azurite escuta nas seguintes portas:
- Blob Storage:
10000
- Queue Storage:
10001
- Table Storage:
10002
3. Verificando o Funcionamento do Azurite
Para garantir que Azurite está funcionando corretamente, você pode usar o Azure Storage Explorer ou outros clientes compatíveis para se conectar ao emulador local. Utilize as credenciais padrão:
- Account Name:
devstoreaccount1
- Account Key:
Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
- Blob Service Endpoint:
http://127.0.0.1:10000/devstoreaccount1
Desenvolvendo a API com FastAPI
1. Instalando Dependências
Crie e ative um ambiente virtual (opcional, mas recomendado):
python -m venv venv
source venv/bin/activate # Para Linux/Mac
# Ou
venv\Scripts\activate # Para Windows
Instale as dependências necessárias usando pip
:
pip install fastapi uvicorn python-multipart azure-storage-blob
Descrição das Dependências:
fastapi
: Framework web para construir APIs rápidas e eficientes.uvicorn
: Servidor ASGI para rodar a aplicação FastAPI.python-multipart
: Necessário para lidar com uploads de arquivos.azure-storage-blob
: Biblioteca oficial da Microsoft para interagir com o Azure Blob Storage.
2. Estrutura do Projeto
Crie um arquivo chamado main.py
e insira o seguinte código:
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse, Response
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
import os
import logging
app = FastAPI()
# Configura o logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configurações do Azurite
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
CONTAINER_NAME = "fotos"
def initialize_blob_service():
"""Inicializa o serviço Blob e o container."""
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(CONTAINER_NAME)
try:
container_client.create_container()
logger.info(f"Container '{CONTAINER_NAME}' criado.")
except Exception as e:
logger.info(f"Container '{CONTAINER_NAME}' já existe ou ocorreu um erro: {e}")
return blob_service_client
except Exception as e:
logger.error(f"Erro ao conectar ao Azurite: {e}")
raise e
# Inicializa o BlobServiceClient
blob_service_client = initialize_blob_service()
@app.post("/upload")
async def upload_photo(file: UploadFile = File(...)):
"""
Endpoint para fazer upload de uma foto.
"""
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Arquivo enviado não é uma imagem.")
blob_name = file.filename
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
# Lê o conteúdo do arquivo
file_content = await file.read()
blob_client.upload_blob(file_content, overwrite=True)
logger.info(f"Foto '{blob_name}' enviada com sucesso.")
return JSONResponse(status_code=200, content={"message": f"Foto '{blob_name}' enviada com sucesso."})
except Exception as e:
logger.error(f"Erro ao enviar a foto: {e}")
raise HTTPException(status_code=500, detail=f"Erro ao enviar a foto: {e}")
@app.get("/photos/{blob_name}")
async def get_photo(blob_name: str):
"""
Endpoint para obter informações sobre uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
logger.info(f"Informações da foto '{blob_name}' recuperadas com sucesso.")
return JSONResponse(status_code=200, content={"blob_name": blob_name, "size": blob.size})
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
@app.get("/download/{blob_name}")
async def download_photo(blob_name: str):
"""
Endpoint para baixar uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
content = blob.readall()
# Detecta o tipo de conteúdo com base na extensão do arquivo
if blob_name.lower().endswith('.png'):
media_type = "image/png"
elif blob_name.lower().endswith('.gif'):
media_type = "image/gif"
else:
media_type = "image/jpeg"
logger.info(f"Foto '{blob_name}' baixada com sucesso.")
return Response(content, media_type=media_type)
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada para download: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
# Função main opcional para rodar com Uvicorn
def main():
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
# Executa main() se o script for executado diretamente
if __name__ == "__main__":
main()
3. Explicação Detalhada do Código
3.1. Importações e Configurações Iniciais
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse, Response
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
import os
import logging
app = FastAPI()
# Configura o logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
- FastAPI e FastAPI Responses: Utilizados para criar os endpoints e enviar respostas JSON ou binárias.
- Azure Storage Blob: Bibliotecas necessárias para interagir com o Azure Blob Storage.
- Logging: Configurado para registrar informações e erros, facilitando a depuração.
3.2. Configurações do Azurite
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
CONTAINER_NAME = "fotos"
- AZURITE_CONNECTION_STRING: String de conexão padrão para Azurite. Assegure-se de que está correta e não contém espaços ou quebras de linha indesejadas.
- CONTAINER_NAME: Nome do container onde as fotos serão armazenadas. Neste caso, “fotos”.
3.3. Inicialização do BlobServiceClient
def initialize_blob_service():
"""Inicializa o serviço Blob e o container."""
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(CONTAINER_NAME)
try:
container_client.create_container()
logger.info(f"Container '{CONTAINER_NAME}' criado.")
except Exception as e:
logger.info(f"Container '{CONTAINER_NAME}' já existe ou ocorreu um erro: {e}")
return blob_service_client
except Exception as e:
logger.error(f"Erro ao conectar ao Azurite: {e}")
raise e
# Inicializa o BlobServiceClient
blob_service_client = initialize_blob_service()
- BlobServiceClient: Cliente principal para interagir com o serviço Blob.
- ContainerClient: Cliente para interagir com um container específico.
- Criação do Container: Tenta criar o container; se já existir, captura a exceção e continua.
3.4. Endpoint /upload
para Upload de Fotos
@app.post("/upload")
async def upload_photo(file: UploadFile = File(...)):
"""
Endpoint para fazer upload de uma foto.
"""
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="Arquivo enviado não é uma imagem.")
blob_name = file.filename
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
# Lê o conteúdo do arquivo
file_content = await file.read()
blob_client.upload_blob(file_content, overwrite=True)
logger.info(f"Foto '{blob_name}' enviada com sucesso.")
return JSONResponse(status_code=200, content={"message": f"Foto '{blob_name}' enviada com sucesso."})
except Exception as e:
logger.error(f"Erro ao enviar a foto: {e}")
raise HTTPException(status_code=500, detail=f"Erro ao enviar a foto: {e}")
- Validação do Tipo de Arquivo: Garante que apenas arquivos de imagem sejam enviados.
- BlobClient: Cliente para interagir com um blob específico dentro do container.
- Upload do Blob: Lê o conteúdo do arquivo e faz o upload para o Azure Blob Storage. O parâmetro
overwrite=True
permite sobrescrever blobs existentes com o mesmo nome. - Resposta JSON: Retorna uma mensagem de sucesso ou um erro apropriado.
3.5. Endpoint /photos/{blob_name}
para Obter Informações da Foto
@app.get("/photos/{blob_name}")
async def get_photo(blob_name: str):
"""
Endpoint para obter informações sobre uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
logger.info(f"Informações da foto '{blob_name}' recuperadas com sucesso.")
return JSONResponse(status_code=200, content={"blob_name": blob_name, "size": blob.size})
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
- Download do Blob: Recupera o blob para obter suas propriedades, como tamanho.
- Resposta JSON: Retorna informações relevantes sobre o blob ou um erro caso não seja encontrado.
3.6. Endpoint /download/{blob_name}
para Baixar a Foto
@app.get("/download/{blob_name}")
async def download_photo(blob_name: str):
"""
Endpoint para baixar uma foto específica.
"""
blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=blob_name)
try:
blob = blob_client.download_blob()
content = blob.readall()
# Detecta o tipo de conteúdo com base na extensão do arquivo
if blob_name.lower().endswith('.png'):
media_type = "image/png"
elif blob_name.lower().endswith('.gif'):
media_type = "image/gif"
else:
media_type = "image/jpeg"
logger.info(f"Foto '{blob_name}' baixada com sucesso.")
return Response(content, media_type=media_type)
except Exception as e:
logger.error(f"Foto '{blob_name}' não encontrada para download: {e}")
raise HTTPException(status_code=404, detail="Foto não encontrada.")
- Download do Conteúdo do Blob: Lê o conteúdo binário do blob.
- Detecção do Tipo de Conteúdo: Baseado na extensão do arquivo, define o tipo MIME apropriado para a resposta.
- Resposta Binária: Retorna a foto com o tipo de mídia correto ou um erro caso não seja encontrada.
3.7. Função main()
para Rodar o Servidor
def main():
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
# Executa main() se o script for executado diretamente
if __name__ == "__main__":
main()
- Uvicorn: Serve a aplicação FastAPI.
- Parâmetros:
"main:app"
: Refere-se ao arquivomain.py
e à instânciaapp
da FastAPI.host
: Endereço IP onde o servidor será executado.port
: Porta onde o servidor escutará as requisições.reload=True
: Permite recarregar automaticamente o servidor quando mudanças no código são detectadas (útil durante o desenvolvimento).
4. Executando a Aplicação
Com o Azurite em execução e as dependências instaladas, inicie o servidor FastAPI executando:
python main.py
Ou, alternativamente, usando o Uvicorn diretamente:
uvicorn main:app --reload
O servidor será iniciado em http://127.0.0.1:8000
. Você pode acessar o Swagger UI para interagir com a API de forma visual em http://127.0.0.1:8000/docs
.
Interagindo com a API usando cURL
Para testar os endpoints da API, você pode usar comandos curl
no terminal. A seguir, apresentamos exemplos para cada um dos endpoints desenvolvidos.
1. Upload de Foto (POST /upload
)
Descrição: Envia uma foto para o container fotos
no Azure Blob Storage.
Comando cURL:
curl -X POST "http://127.0.0.1:8000/upload" \
-H "Content-Type: multipart/form-data" \
-F "file=@/caminho/para/sua/foto.jpg"
plicação dos Parâmetros:
-X POST
: Especifica que a requisição é do tipo POST."http://127.0.0.1:8000/upload"
: URL do endpoint de upload.-H "Content-Type: multipart/form-data"
: Define o tipo de conteúdo como multipart/form-data, necessário para upload de arquivos.-F "file=@/caminho/para/sua/foto.jpg"
: Anexa o arquivo que será enviado. Substitua"/caminho/para/sua/foto.jpg"
pelo caminho real do arquivo que deseja fazer upload.
Exemplo Prático:
Se você tem uma foto chamada imagem.jpg
no diretório atual:
curl -X POST "http://127.0.0.1:8000/upload" \
-H "Content-Type: multipart/form-data" \
-F "file=@./imagem.jpg"
Resposta Esperada:
{
"message": "Foto 'imagem.jpg' enviada com sucesso."
}
2. Obter Informações da Foto (GET /photos/{blob_name}
)
Descrição: Recupera informações sobre uma foto específica armazenada no container fotos
.
Comando cURL:
curl -X GET "http://127.0.0.1:8000/photos/imagem.jpg"
Explicação:
- Substitua
imagem.jpg
pelo nome real da foto que você enviou.
Exemplo Prático:
curl -X GET "http://127.0.0.1:8000/photos/imagem.jpg"
Resposta Esperada:
{
"blob_name": "imagem.jpg",
"size": 102400
}
Onde size
representa o tamanho do arquivo em bytes.
3. Baixar a Foto (GET /download/{blob_name}
)
Descrição: Baixa uma foto específica armazenada no container fotos
.
Comando cURL:
curl -X GET "http://127.0.0.1:8000/download/imagem.jpg" --output imagem_baixada.jpg
Resposta Esperada:
A foto será baixada e salva como imagem_baixada.jpg
no diretório atual.
Solução de Problemas Comuns
Durante o desenvolvimento, é comum encontrar erros ou comportamentos inesperados. A seguir, listamos alguns problemas comuns e como resolvê-los.
1. Erro de Autenticação
Erro Recebido:
{
"detail": "Erro ao enviar a foto: Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.\nRequestId:c1be2774-598e-455c-984f-6ceb73d00274\nTime:2024-11-02T12:19:02.237Z\nErrorCode:AuthorizationFailure\nContent: <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Error>\n <Code>AuthorizationFailure</Code>\n <Message>Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.\nRequestId:c1be2774-598e-455c-984f-6ceb73d00274\nTime:2024-11-02T12:19:02.237Z</Message>\n</Error>"
}
Solução:
Esse erro indica que o cabeçalho de Authorization está incorretamente formado ou que a assinatura está errada. Siga estes passos para resolver:
- Verifique a String de Conexão:
- Assegure-se de que a
AZURITE_CONNECTION_STRING
está correta e sem espaços extras ou quebras de linha. A string de conexão padrão para Azurite é:
DefaultEndpointsProtocol=http;
AccountName=devstoreaccount1;
AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;
BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;
Exemplo Correto em Python:
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
Certifique-se de que o Azurite Está em Execução:
Verifique se o Azurite está rodando e ouvindo na porta correta (10000
para Blob Storage). Se não estiver, execute novamente:
azurite --silent --location ./azurite --debug ./azurite/debug.log
- Verifique as Credenciais:
- As credenciais padrão para Azurite são:
- Account Name:
devstoreaccount1
- Account Key:
Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
- Atualize a Biblioteca
azure-storage-blob
: - Certifique-se de que você está usando a versão mais recente da biblioteca:
pip install --upgrade azure-storage-blob
2. Verifique os Logs
Adicione logs detalhados para facilitar a identificação de onde o problema está ocorrendo. No código fornecido, já utilizamos o módulo logging
para registrar informações e erros. Verifique os logs no console onde o FastAPI está rodando e no arquivo de log do Azurite (./azurite/debug.log
).
3. Teste com um Script Simples
Antes de integrar tudo na API, teste a conexão com Azurite usando um script Python simples:
from azure.storage.blob import BlobServiceClient
AZURITE_CONNECTION_STRING = (
"DefaultEndpointsProtocol=http;"
"AccountName=devstoreaccount1;"
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;"
"BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
)
def test_connection():
try:
blob_service_client = BlobServiceClient.from_connection_string(AZURITE_CONNECTION_STRING)
containers = blob_service_client.list_containers()
print("Containers existentes:")
for container in containers:
print(f"- {container['name']}")
except Exception as e:
print(f"Erro ao conectar ou listar containers: {e}")
if __name__ == "__main__":
test_connection()
Execute o Script:
python test_connection.py
Objetivo:
- Verificar se a conexão com Azurite está funcionando.
- Listar os containers existentes para confirmar se o container
fotos
está presente.
4. Verifique o Relógio do Sistema
Embora esteja usando Azurite localmente, é importante garantir que o relógio do sistema esteja sincronizado. Diferenças significativas no tempo podem causar falhas de autenticação.
5. Permissões e Firewall
Certifique-se de que nenhuma configuração de firewall ou segurança está bloqueando as portas usadas pelo Azurite (10000
) e pelo FastAPI (8000
).
Conclusão
Neste artigo, exploramos como configurar uma API robusta usando FastAPI que interage com o Azure Blob Storage via Azurite. Desenvolvemos endpoints para upload, obtenção de informações e download de fotos, além de fornecer exemplos práticos de como interagir com esses endpoints usando comandos curl
.
O uso do Azurite permite que desenvolvedores testem e desenvolvam localmente sem a necessidade de uma conta Azure, acelerando o processo de desenvolvimento e evitando custos desnecessários durante a fase inicial. Integrar o FastAPI com o Azure Blob Storage oferece uma solução poderosa para aplicações que exigem armazenamento eficiente e escalável de arquivos.
Próximos Passos:
- Deploy na Nuvem: Após testar localmente com Azurite, você pode migrar para o Azure Blob Storage real, ajustando a string de conexão conforme necessário.
- Autenticação e Segurança: Implemente mecanismos de autenticação para proteger seus endpoints.
- Validações Adicionais: Adicione validações mais rigorosas para os arquivos enviados, como tamanho máximo e tipos de arquivos permitidos.
- Monitoramento e Logs Avançados: Utilize ferramentas de monitoramento para acompanhar o desempenho e identificar problemas em produção.
Desenvolver uma API integrada com serviços de armazenamento em nuvem, como o Azure Blob Storage, é uma habilidade valiosa que pode aprimorar significativamente a capacidade e a escalabilidade de suas aplicações. Com as ferramentas e conhecimentos adquiridos neste artigo, você está bem encaminhado para construir soluções eficientes e escaláveis.
⚡ Azure Queue vs AWS SQS, Azure Table vs AWS DynamoDB, e Azure Blob vs AWS S3: Qual escolher? Azure Queue Storage: Integrado ao ecossistema Azure, ideal para workloads leves. Menos funcionalidades comparado ao SQS e integração limitada fora do Azure. AWS SQS: Funcionalidades robustas, suporte a dead-letter queues e alta escalabilidade. Custos elevados para grandes workloads e dependência de outros serviços AWS. Azure Table Storage: Simples, bom para soluções de baixo custo, mas limitado em queries complexas e escalabilidade global. AWS DynamoDB: Autoescalabilidade, índices secundários e alta disponibilidade. Custos altos e complexidade na configuração. Azure Blob Storage vs AWS S3: Azure Blob Storage: ▶ Vantagens: Excelente integração com serviços Azure; custo acessível para armazenamento em camadas (hot, cool, archive); ideal para armazenar grandes volumes de dados não estruturados. ▶ Desvantagens: Algumas funcionalidades, como replicação geográfica, podem ser mais limitadas em comparação ao S3. AWS S3: ▶ Vantagens: Maturidade e maior variedade de opções de replicação e armazenamento; integração ampla com o ecossistema AWS e com ferramentas de terceiros; suporte a funcionalidades como S3 Glacier para arquivamento. ▶ Desvantagens: Custos podem escalar rapidamente dependendo da frequência de acesso aos dados e dos serviços adicionais utilizados; maior complexidade de gerenciamento para usuários que não estão familiarizados com o ecossistema AWS. A escolha depende da sua arquitetura e necessidades de integração! Vantagens e Desvantagens Competitivas do Azure para Empresas de Médio/Grande Porte Para uma empresa de médio ou grande porte, a escolha entre as ferramentas do Azure — como Azure Queue, Azure Table e Azure Blob Storage — pode oferecer vantagens e desvantagens competitivas em relação aos outros grandes players de nuvem, como AWS e Google Cloud. 1. Azure Queue Storage Vantagem Competitiva: Empresas que já estão altamente integradas ao ecossistema Azure podem se beneficiar da simplicidade de integração do Azure Queue Storage, reduzindo a complexidade de gestão de mensagens entre diferentes serviços e mantendo uma arquitetura mais unificada. Isso pode resultar em eficiência operacional e menores custos de gerenciamento. Desvantagem Competitiva: Quando comparado com AWS SQS, Azure Queue oferece menos funcionalidades, o que pode ser um desafio para empresas que necessitam de features mais robustas, como suporte nativo a dead-letter queues ou integrações com serviços que vão além da plataforma Azure. Isso pode limitar a flexibilidade e a capacidade de expansão para outras nuvens ou plataformas. 2. Azure Table Storage Vantagem Competitiva: Azure Table Storage é uma solução de baixo custo para armazenamento de dados no formato NoSQL, ideal para empresas que buscam simplicidade e que não precisam de consultas complexas. Para aplicações que lidam com grandes volumes de dados, mas com baixa frequência de consulta, é uma alternativa eficaz que pode reduzir custos. Desvantagem Competitiva: Em contrapartida, AWS DynamoDB oferece recursos de escalabilidade automática e índices secundários que o tornam mais adequado para aplicações com alta demanda de consultas dinâmicas e complexas. Empresas que buscam um sistema que possa crescer de maneira flexível em resposta às demandas do mercado podem encontrar limitações no Azure Table Storage. 3. Azure Blob Storage Vantagem Competitiva: Azure Blob Storage é particularmente vantajoso para empresas que já utilizam outros serviços Azure, como Azure Machine Learning ou Azure Data Factory, devido à excelente integração e facilidade de uso. Além disso, o armazenamento em camadas (hot, cool, archive) permite uma otimização dos custos de acordo com a frequência de acesso aos dados, o que é uma vantagem significativa para grandes corporações que precisam gerenciar vastos volumes de dados não estruturados. Desvantagem Competitiva: AWS S3, no entanto, possui um histórico mais maduro e um conjunto mais amplo de funcionalidades, como opções de replicação e integrações nativas com diversos serviços AWS e de terceiros. Empresas que buscam flexibilidade para integrações multicloud ou recursos mais sofisticados podem preferir o S3 em relação ao Azure Blob. Conclusão Para uma empresa de médio/grande porte, a escolha das soluções de armazenamento e processamento de mensagens do Azure dependerá diretamente da sua infraestrutura atual e dos seus planos de expansão. A vantagem competitiva do Azure está na simplicidade de integração e no custo-benefício, especialmente se a empresa já utiliza outras soluções da plataforma. Por outro lado, a AWS oferece recursos mais robustos e maior flexibilidade para empresas que planejam escalar rapidamente ou que precisam de integrações mais complexas, tornando a escolha um balanço entre custo, simplicidade e capacidade de expansão.
Conclusão
Neste artigo, exploramos como configurar uma API robusta usando FastAPI que interage com o Azure Blob Storage via Azurite. Desenvolvemos endpoints para upload, obtenção de informações e download de fotos, além de fornecer exemplos práticos de como interagir com esses endpoints usando comandos curl
.
O uso do Azurite permite que desenvolvedores testem e desenvolvam localmente sem a necessidade de uma conta Azure, acelerando o processo de desenvolvimento e evitando custos desnecessários durante a fase inicial. Integrar o FastAPI com o Azure Blob Storage oferece uma solução poderosa para aplicações que exigem armazenamento eficiente e escalável de arquivos.
Próximos Passos:
- Deploy na Nuvem: Após testar localmente com Azurite, você pode migrar para o Azure Blob Storage real, ajustando a string de conexão conforme necessário.
- Autenticação e Segurança: Implemente mecanismos de autenticação para proteger seus endpoints.
- Validações Adicionais: Adicione validações mais rigorosas para os arquivos enviados, como tamanho máximo e tipos de arquivos permitidos.
- Monitoramento e Logs Avançados: Utilize ferramentas de monitoramento para acompanhar o desempenho e identificar problemas em produção.
Desenvolver uma API integrada com serviços de armazenamento em nuvem, como o Azure Blob Storage, é uma habilidade valiosa que pode aprimorar significativamente a capacidade e a escalabilidade de suas aplicações. Com as ferramentas e conhecimentos adquiridos neste artigo, você está bem encaminhado para construir soluções eficientes e escaláveis.