Skip to main content

Arquitetura

O raro-reviewer segue o padrão Ports & Adapters (Hexagonal Architecture), separando a lógica de negócio das dependências externas.

Diagrama de Componentes

┌─────────────────────────────────────────────────────────────────┐
│ Entrypoints │
│ ┌──────────────┐ ┌───────────────────────┐ │
│ │ app.py │ (Container) │ lambda_handler.py │ │
│ │ uvicorn │ │ Mangum + Lambda │ │
│ └──────┬───────┘ └──────────┬────────────┘ │
│ │ │ │
│ └───────────────┬───────────────────────┘ │
│ │ │
│ ┌────▼────┐ │
│ │webapp.py│ create_app() factory │
│ └────┬────┘ │
│ │ │
│ ┌────▼────────┐ │
│ │webhook/ │ Token validation + routing │
│ │router.py │ │
│ └────┬────────┘ │
│ │ │
│ ┌────▼────────┐ │
│ │ Dispatcher │ Fire-and-forget │
│ └────┬────────┘ │
│ │ │
├─────────────────────────┼───────────────────────────────────────┤
│ Core (Pure Python) │
│ ┌────▼────────────┐ │
│ │ReviewPipeline │ Orchestration │
│ └────┬────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ┌────▼────┐ ┌─────▼─────┐ ┌────▼──────┐ │
│ │ Diff │ │ Position │ │ Finding │ │
│ │ Parser │ │ Mapper │ │ Models │ │
│ └─────────┘ └───────────┘ └───────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ Ports (Interfaces) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ GitlabClient │ │ LlmClient │ │ Dispatcher │ │
│ │ (Protocol) │ │ (Protocol) │ │ (Protocol) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
├───────────┼────────────────┼────────────────┼───────────────────┤
│ Adapters (Implementations) │
│ ┌──────▼───────┐ ┌──────▼───────┐ ┌──────▼───────┐ │
│ │GitlabRest │ │ NvidiaNim │ │ Background │ │
│ │Client │ │ Client │ │ TaskDispatch │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐│
│ │LambdaSelf ││
│ │InvokeDispatch││
│ └──────────────┘│
└─────────────────────────────────────────────────────────────────┘

Camadas

Core (src/core/)

Lógica de negócio pura, sem dependências de frameworks externos.

MóduloResponsabilidade
models.pyDataclasses: MrEvent, FileDiff, DiffLine, Finding
diff_parser.pyParse de diffs unificados do GitLab
position_mapper.pyMapeamento de findings para posições inline no GitLab
review_pipeline.pyOrquestração: fetch diff → LLM review → post comments

Ports (src/ports/)

Interfaces (Protocol classes) que definem os contratos entre core e adapters.

PortaMétodo Principal
GitlabClientfetch_diff(), post_inline_comment()
LlmClientreview_file()
Dispatcherdispatch()

Adapters (src/adapters/)

Implementações concretas das ports.

AdapterImplementa
gitlab_rest_client.pyGitlabClient via REST API
nvidia_nim_client.pyLlmClient via OpenAI SDK
background_tasks.pyDispatcher via asyncio (container)
lambda_self_invoke.pyDispatcher via Lambda self-invoke

Entrypoints

ArquivoAmbienteDescrição
app.pyContaineruvicorn app:app — lê env vars diretamente
lambda_handler.pyAWS LambdaBusca secrets do Secrets Manager no cold start

Fluxo de um Webhook

1. GitLab POST /webhook

2. webhook/router.py valida X-Gitlab-Token

3. parse_mr_event() extrai MrEvent do payload

4. dispatcher.dispatch(event) → fire-and-forget

5. ReviewPipeline.run(event)

├── action == "open" → gitlab.fetch_diff()
└── action == "update" → gitlab.fetch_compare(oldrev, newrev)

6. Para cada FileDiff:

├── Tamanho > 800 linhas? → skip

└── llm.review_file(event, file_diff)

├── LlmParseError → skip arquivo
└── findings → para cada finding:

├── position_mapper → linha existe no diff?
│ ├── sim → post_inline_comment()
│ └── não → post_fallback_comment()

└── erro no post → log, não aborta

Princípios de Design

  1. Core puro: src/core nunca importa boto3, fastapi, openai
  2. Protocol-based: Contratos definidos com typing.Protocol
  3. Fail-isolated: Erro em um arquivo não afeta outros
  4. Stateless: Sem banco de dados, sem cache entre requests
  5. Dual-target: Mesmo código, dois entrypoints (container e Lambda)