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.pydefine 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.pyimplementa o caso de usoAddAccounte utiliza umAccountRepositorypara persistir os dados.protocols/db/: Define os contratos (interfaces) para os repositórios de dados. Por exemplo,account_repository.pydefine 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 interfaceAccountRepositoryusando 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, comoControllereHttpRequest/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.pycria a instância doSignUpControllercom 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
UsereValidUserdemain/server.pyparadomain/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.pyque 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.pypara 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.pyque 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.pypara 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 oDbApproveAccounte retornar a resposta HTTP apropriada.
- Crie um arquivo
-
Criar Factory (Main):
- Crie um arquivo
main/factories/controllers/approve_account_controller_factory.pypara instanciar oApproveAccountControllercom todas as suas dependências (DbApproveAccounteAccountRepositoryMock).
- Crie um arquivo
-
Atualizar
server.py(Main):- Remova a rota
POST /ch01/login/validatee 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.