Pular para conteúdo

Camadas

O Spryx Backend segue a Clean Architecture com fronteiras estritas entre camadas. Cada camada tem uma única responsabilidade e depende apenas das camadas abaixo dela.

Diagrama de Camadas

flowchart TD
  A["HTTP Controllers\nsrc/entrypoints/http/"]
  B["Use Cases · Query Handlers\nsrc/modules/{module}/application/"]
  C["Services\nsrc/modules/{module}/application/services/"]
  D["Repositories\nsrc/modules/{module}/infrastructure/"]
  E[("PostgreSQL\nvia Supabase")]

  A -->|"chama UC ou Query Handler"| B
  B -->|"delega lógica reutilizável"| C
  B -->|"lê / escreve"| D
  C -->|"lê / escreve"| D
  D -->|"SQL via AsyncSession"| E

Responsabilidades por Camada

HTTP Controllers (src/entrypoints/http/)

  • Recebem requisições HTTP, validam schemas de entrada
  • Chamam um Use Case ou Query Handler
  • Retornam respostas HTTP
  • Nunca contêm lógica de negócio ou acesso ao banco

Use Cases (application/ucs/)

  • Possuem a fronteira da transação via Unit of Work
  • Orquestram services e repositories
  • Aplicam regras de negócio e idempotência
  • Cada UC é uma pasta com 3 arquivos: command.py, result.py, uc.py
  • Nunca chamam outros Use Cases — extraia lógica compartilhada para um Service

Query Handlers (application/queries/)

  • Operações somente leitura otimizadas para endpoints de listagem
  • Usam SQL puro para performance (joins, CTEs, paginação)
  • Retornam Views/DTOs, nunca entidades de domínio
  • Chamados diretamente por controllers para endpoints GET /list

Services (application/services/)

  • Lógica de negócio reutilizável compartilhada entre múltiplos Use Cases
  • Recebem tx: Transaction do UC
  • Podem coordenar múltiplos repositories

Repositories (infrastructure/)

  • Apenas persistência — sem lógica de negócio
  • Recebem tx: Transaction como primeiro parâmetro
  • Seguros por padrão para multi-tenant: leituras requerem tenant_id, escritas usam entity.tenant_id
  • Classes concretas apenas (sem ABC/interface)

Padrão de Transação (Unit of Work)

Todo write no banco passa por uma transação de propriedade do Use Case.

sequenceDiagram
  participant C as Controller
  participant UC as Use Case
  participant UoW as Unit of Work
  participant Repo as Repository
  participant DB as PostgreSQL

  C->>UC: execute(command)
  UC->>UoW: abre transação
  UoW->>Repo: save(tx, entity)
  Repo->>DB: INSERT / UPDATE
  UoW->>UC: commit
  UC->>C: result
  Note over UC: side effects (email, webhook)<br/>acontecem APÓS o commit

Regra das três zonas:

  1. Antes da transação — chamadas externas preparatórias (vault, APIs externas)
  2. Dentro da transação — todos os writes SQL e leituras necessárias
  3. Após o commit — side effects (notificações, webhooks, emails)

Padrões Principais

IDs Tipados

Todas as entidades usam IDs baseados em PrefixedULID para type safety:

AgentID      # "ag_01HQ..."
TenantID     # "tenant_01HQ..."
WorkflowID   # "wf_01HQ..."
ContactID    # "contact_01HQ..."

Previne mistura de IDs entre tipos de entidade em tempo de compilação.

Entidades Imutáveis

Entidades de domínio são modelos Pydantic atualizados via model_copy:

updated = agent.model_copy(update={"name": "Novo Nome", "status": AgentStatus.ACTIVE})

Sem mutação no lugar — toda mudança retorna uma nova instância.

Comunicação Entre Módulos

Módulos nunca importam uns dos outros. Dependências cross-module passam por Ports (interfaces em src/modules/shared/ports/) conectados via DI:

módulo agent → KbSearchPort → (resolvido em runtime) → KbSearchAdapter