Este documento descreve a arquitetura de software do projeto, baseada em princípios de Clean Architecture (Arquitetura Limpa) e design em camadas. O objetivo é criar um sistema desacoplado, testável e de fácil manutenção.
O projeto está organizado em camadas, cada uma com uma responsabilidade clara e bem definida. As dependências fluem sempre em direção ao centro (das camadas externas para as internas), ou seja, Presentation
-> Data
-> Domain
. A camada Infra
implementa abstrações definidas nas camadas mais internas, invertendo o controle.
graph TD
subgraph "Presentation Layer"
A[Controllers]
end
subgraph "Data Layer"
B[Use Cases Impl.]
end
subgraph "Domain Layer"
C[Use Cases Abstractions]
D[Models]
end
subgraph "Infra Layer"
E[DB Repositories]
F[Webserver]
end
subgraph "Main Layer (Composition Root)"
G[Factories]
end
G --> A
A --> B
B --> C
B --> E
E --> D
F --> A
- Responsabilidade: Contém a lógica de negócio principal e as entidades do sistema. Esta é a camada mais interna e pura da aplicação. Ela não deve ter conhecimento sobre banco de dados, frameworks web ou qualquer outra tecnologia externa.
- Diretório:
domain/
- Conteúdo:
models/
: Define as estruturas de dados e entidades do negócio (ex:Account
).use_cases/
: Define as interfaces (contratos) para as ações de negócio que o sistema pode realizar (ex:add_account.py
define o que é necessário para adicionar uma conta, sem se preocupar em como isso será feito).
- Responsabilidade: Orquestra a execução dos casos de uso. Esta camada implementa as interfaces de casos de uso definidas no
Domain
. Ela atua como uma ponte, conectando a lógica de negócio pura com as abstrações de acesso a dados. - Diretório:
data/
- Conteúdo:
use_cases/
: Implementações concretas dos casos de uso. Por exemplo,db_add_account.py
implementa o caso de usoAddAccount
e utiliza umAccountRepository
para persistir os dados.protocols/db/
: Define os contratos (interfaces) para os repositórios de dados. Por exemplo,account_repository.py
define quais métodos um repositório de contas deve ter (ex:add
,find_by_email
).
- Responsabilidade: Contém as implementações concretas de tecnologias e frameworks externos. Isso inclui acesso a banco de dados, clientes HTTP, sistemas de mensageria e o próprio framework web. Esta camada implementa as interfaces de repositório definidas na camada
Data
. - Diretório:
infra/
- Conteúdo:
db/
: Implementações concretas dos repositórios.nsql_mock/account_repository.py
é um exemplo que implementa a interfaceAccountRepository
usando um mock em memória, o que é excelente para testes e desenvolvimento inicial.webserver/
: Configuração e código relacionado ao framework web (FastAPI).
- Responsabilidade: É a camada mais externa, responsável por interagir com o "mundo exterior" (neste caso, via HTTP). Ela recebe as requisições, extrai e valida os dados, e chama os casos de uso (da camada
Data
) para executar a lógica de negócio. - Diretório:
presentation/
- Conteúdo:
controllers/
: Controladores que recebem as requisições HTTP, delegam a ação para um caso de uso e formatam a resposta HTTP. Osignup_controller.py
é um exemplo perfeito disso.protocols/
: Define contratos para os componentes desta camada, comoController
eHttpRequest
/HttpResponse
.
- Responsabilidade: É o ponto de entrada da aplicação. Sua única função é "compor" o sistema, ou seja, instanciar as classes de cada camada e injetar as dependências corretamente.
- Diretório:
main/
- Conteúdo:
server.py
: Inicia o servidor web.factories/
: Fábricas que constroem os controladores, injetando neles as implementações concretas dos casos de uso e repositórios. Por exemplo,signup_controller_factory.py
cria a instância doSignUpController
com todas as suas dependências.
O endpoint POST /ch01/login/signup
já segue corretamente a arquitetura descrita, utilizando um Controller
que chama um UseCase
através de uma Factory
. No entanto, todos os outros endpoints definidos em main/server.py
violam essa arquitetura.
O Problema:
O arquivo main/server.py
atualmente mistura responsabilidades de todas as camadas:
- Presentation: Define dezenas de rotas FastAPI.
- Domain: Define modelos Pydantic como
User
,UserProfile
, etc. - Data/Infra: Implementa a lógica de negócio e o armazenamento de dados diretamente nas funções das rotas, usando dicionários globais (
valid_users
,pending_users
) como banco de dados em memória.
Isso torna o código difícil de testar, manter e evoluir.
A seguir, um plano de ação para migrar a lógica legada para a arquitetura de camadas, usando o endpoint POST /ch01/login/validate
como exemplo. O mesmo padrão deve ser aplicado aos demais.
Passo a Passo para Refatorar approve_user
:
-
Mover Modelos:
- Mova as classes Pydantic
User
eValidUser
demain/server.py
paradomain/models/account.py
(ou um novo arquivo apropriado emdomain/models/
).
- Mova as classes Pydantic
-
Criar Use Case (Domain):
- Crie um arquivo
domain/use_cases/approve_account.py
que define a interface do caso de uso:from abc import ABC, abstractmethod from domain.models.account import ValidUser, User class ApproveAccount(ABC): @abstractmethod def approve(self, user: User) -> ValidUser: raise NotImplementedError
- Crie um arquivo
-
Abstrair Repositório (Data):
- Modifique o protocolo
data/protocols/db/account/account_repository.py
para incluir métodos para encontrar, salvar e remover usuários, que serão usados pelo caso de uso.
- Modifique o protocolo
-
Implementar Use Case (Data):
- Crie o arquivo
data/use_cases/db_approve_account.py
que implementa a interfaceApproveAccount
. Ele receberá instâncias de repositórios em seu construtor para interagir com os dados.
- Crie o arquivo
-
Implementar Repositório (Infra):
- Modifique
infra/db/nsql_mock/account_repository.py
para implementar os novos métodos da interface do repositório, utilizando os dicionários que hoje são globais emserver.py
.
- Modifique
-
Criar Controller (Presentation):
- Crie um arquivo
presentation/controllers/auth/approve_account_controller.py
. Este controlador irá receber a requisição HTTP, chamar oDbApproveAccount
e retornar a resposta HTTP apropriada.
- Crie um arquivo
-
Criar Factory (Main):
- Crie um arquivo
main/factories/controllers/approve_account_controller_factory.py
para instanciar oApproveAccountController
com todas as suas dependências (DbApproveAccount
eAccountRepositoryMock
).
- Crie um arquivo
-
Atualizar
server.py
(Main):- Remova a rota
POST /ch01/login/validate
e toda a sua lógica demain/server.py
. - Importe a nova factory e registre a rota, associando-a ao controlador criado:
# Em main/server.py from main.factories.controllers.approve_account_controller_factory import create_approve_account_controller approve_account_controller = create_approve_account_controller() # Adapte para que o controller seja chamado pela rota # Exemplo: app.post("/ch01/login/validate")(adapt_route(approve_account_controller))
- Remova a rota
Repita este processo para cada endpoint legado em main/server.py
. Ao final, este arquivo deverá conter apenas a inicialização do app e o registro das rotas a partir das factories, sem nenhuma lógica de negócio.