Projeto de Books com Azurite e Docker Compose: Uma Jornada Prática
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.
Estrutura do Projeto
A estrutura do projeto é organizada da seguinte forma:
- Books API: API FastAPI para gerenciar livros, incluindo todas as operações CRUD.
- Worker Book: Um worker que consome mensagens da fila e salva os dados na tabela.
- Azurite: Emulador do Azure Storage para realizar testes localmente.
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
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.
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.
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.
Contribuição
Contribuições são muito bem-vindas! Se desejar melhorar o projeto ou reportar um problema, sinta-se à vontade para enviar um Pull Request ou abrir uma Issue no repositório.
Contato
Caso tenha dúvidas ou precise de mais informações, entre em contato pelo email: andy2903.alp@gmail.com.
Considerações Finais
Este projeto é uma ótima oportunidade para desenvolver habilidades em arquitetura de microsserviços e integração com Azure, além de ser uma excelente aplicação prática de FastAPI e Docker. Espero que este artigo tenha sido útil e que você se sinta inspirado a explorar e contribuir com o projeto!