Python

21 mai, 2025

Implementando Clean Architecture em Python

Publicidade

Veja nesse artigo um passo a passo de como criar uma arquitetura reutilizável utilizando Python

Arquitetura Limpa com Python

Clean Architecture tem sido um dos tópicos de grande interesse na comunidade de desenvolvimento de software por sua promessa de criar sistemas mais sustentáveis, testáveis e flexíveis.

Neste artigo, exploraremos como implementar Clean Architecture em um projeto Python, focando em um domínio de simples.

É importante destacar que a Arquitetura Limpa (Clean Architecture) pode ser aplicada em qualquer linguagem de programação. Neste artigo, escolhi apresentá-la por meio de exemplos em Python. No entanto, todos os conceitos e exemplos mostrados podem ser facilmente adaptados e replicados em outras linguagens

Bom, mas antes o que é Clean Architecture ?

Proposta por Robert C. Martin, a Clean Architecture visa separar as preocupações de uma aplicação, organizando o código de forma que as regras de negócio não sejam afetadas por detalhes como banco de dados, interfaces de usuário ou quaisquer fatores externos.

Essa abordagem facilita a manutenção e a expansão do código, além de tornar os testes mais acessíveis.

E pessoal, para que possamos desenvolver uma arquitetura robusta, os testes são de extrema importancia. Eles ajudam a garantir que o seu código seja manutenível.

Para o exemplo prático, nós construiremos um projeto que nos possibilita consultar o saldo de uma conta financeira.

A conta deve ter:

  1. Titular
  2. Saldo

Bom, vejamos quais são as camadas desta arquitetura:

  • Domain: Contém as entidades e regras de negócio.
  • Application: Inclui os casos de uso, orquestrando o fluxo de dados entre a interface do usuário e a camada de domínio.
  • Adapters: Transfere dados entre a aplicação e serviços externos.
  • Infra ou Infrastructure: Lida com detalhes de implementação, como acesso ao banco de dados e configurações de framework.
  • Tests: Para resposável pelos testes unitarios, de integração…etc

A seguir você tem um exemplo demonstrando esta estrutura básica:

clean_architecture/
├── domain/entities
├── application/
├── adapters/
└── infrastructure/
└── tests/

E os pacotes necessários para executar aplicação:

#./requirements.txt
fastapi==0.68.0
uvicorn==0.15.0
pytest==6.2.5
isNullOrEmpty==1.0.0

Executando o comando pip install -r requirements.txt você pode baixar todos pacotes para o seu projeto.

Vejamos a seguir a implementação de cada uma destas camadas.

Domain

Iniciando pelo CORE principal do nosso sistema (o motivo dele existir), vejamos como criar/modelar o nosso dominio.

Vamos criar dois novos arquivos, uma para nossa Conta e um para nossa interface IContasRepository que é responsável por abstrair o acesso ao repositório de contas:

# domain/entities/conta.py
class Conta:
    def __init__(self, id,titular, saldo):
        self.id = id
        self.titular = titular        
        self.saldo = saldo

Agora vamos criar a nossa interface IContasRepository:

# domain/interfaces/repositories.py
from abc import ABC, abstractmethod

class IContasRepository(ABC):
    @abstractmethod
    def obter_conta_por_id(self, conta_id: int):
        pass

Analisando este trecho de código, nós estamos utilizando os pacotes ABC e abstractmethod que são componentes da biblioteca padrão do Python para definir uma interface abstrata, conhecida como classe base abstrata (ABC).

Esse método de design é empregado para estabelecer um contrato para subclasses, garantindo que métodos específicos (nesse caso, obter_conta_por_id) sejam implementados pelas subclasses concretas.

Application

Agora vamos implementar o caso de uso ConsultarSaldoUseCase, utilizando a interface do repositório criada no passo anterior:

# application/use_cases/consultar_saldo.py
from domain.interfaces.repository import IContasRepository
from isNullOrEmpty.is_null_or_empty import is_null_or_empty

class ConsultarSaldoUseCase:
    def __init__(self, repo: IContasRepository):
        self.repo = repo

    def execute(self, conta_id: int) -> float:
        conta = self.repo.obter_conta_por_id(conta_id)
        if is_null_or_empty(conta):
            raise Exception("Conta não encontrada.")
        return conta.saldo

Neste trecho de código estamos implementando a interface `IContasRepository` e criando o método execute para o nosso UseCase.

Adapters

O próximo passo será a a implementação da camada de Adapters, isso inclui implementações concretas dos repositórios, e a Infrastructure configurada do ambiente técnico, como banco de dados e servidor web:

# adapters/repository/contas_repository.py
from domain.entities.conta import Conta
from domain.interfaces.repositories import IContasRepository

class ContasRepositoryMemoria(IContasRepository):
    def obter_conta_por_id(self, conta_id: int) -> Conta:
        return Conta(conta_id,"Thiago Adriano", 100.0)

Neste exemplo estamos simulando uma pesquisa no banco de dados para retornar o número da conta e o valor (mocado) de 100.0.

Infrastructure

Para deixar o nosso código organizado, vejamos como adicionar log nele.

Crie um novo arquivo chamado logging_config.py e atualize ele com o seguinte trecho de código:

# infrastructure/logging/logging_config.py
import logging

def setup_logging():
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

Integrando com FastAPI

Com as camadas anteriores OK, vamos testar o nosso código.

Para isso, vamos utilizar a biblioteca FastAPI para expor o caso de uso ConsultarSaldoUseCase como um endpoint API:

# main.py
from fastapi import FastAPI, HTTPException
from application.use_cases.consultar_saldo import ConsultarSaldoUseCase
from adapters.repository.contas_repository import ContasRepositoryMemoria
from infrastructure.logging.logging_config import setup_logging
import logging

setup_logging()
logger = logging.getLogger(__name__)

app = FastAPI(title="Finanças Clean Architecture", version="1.0.0")

contas_repo = ContasRepositoryMemoria()

@app.on_event("startup")
async def startup_event():
    logger.info("Iniciando aplicação...")

@app.get("/contas/{conta_id}/saldo", summary="Consulta o saldo de uma conta")
def consultar_saldo(conta_id: int):
    logger.info(f"Recebida solicitação de saldo para a conta {conta_id}")
    use_case = ConsultarSaldoUseCase(contas_repo)
    try:
        saldo = use_case.execute(conta_id)
        logger.info(f"Saldo consultado para conta {conta_id}: {saldo}")
        return {"conta_id": conta_id, "saldo": saldo}
    except Exception as e:
        logger.error(f"Erro ao consultar saldo para conta {conta_id}: {e}", exc_info=True)
        raise HTTPException(status_code=404, detail=str(e))

@app.on_event("shutdown")
def shutdown_event():
    logger.info("Finalizando aplicação...")

Neste trecho de código nós estamos inicializando o log, o FastAPI e criando o metodo get para consulta de saldo.

Para que possamos testar, execute o seguinte trecho de código no seuu terminal:

uvicorn main:app --reload

Abra o endereço: http://localhost:8000/docs

Agora você pode utilizar o Swagger para validar a sua implementação 🙂

Caso tenha interesse em clonar a versão final do projeto criado para este artigo, segue seu link no meu GitHub: py-clean-architecture

Bom, espero que tenham gostado pessoal e até um próximo artigo 🙂