Parte III — Galhos, seiva e sementes
06 · Os galhos: serviços, API e CLI
Do tronco saem poucos galhos grossos, e deles muitos finos. No Baobáxia, um serviço grande (
SaberService) e muitos serviços pequenos ao redor dele.
Arquivos para ler
services/saber_service.py—SaberService(≈ 4000 linhas: leia por seção, os comentários# ==== ESCRITA: … ====são o sumário).api/v3/app.py— a montagem da aplicação, middlewares, handlers de erro.api/v3/deps.py— o serviço global e omocde cada requisição.- Uma rota de cada router:
saberes.py,estrutura.py,colecoes.py,sync.py,pedidos.py,historico.py. services/jobs.py,services/cache_leitura.py,infra/vigia_api.py.publica/operacoes.py+cli.py— a mesma operação servindo CLI e bot.applications/— as lentes (acervo, blog, mapa, timeline, agenda).
O mapa da API
| Router | Rotas | Para quê |
|---|---|---|
saberes.py | 35 | CRUD, anexos, thumb, legenda, texto, prévia, cópia/mover, proteção |
estrutura.py | 32 | galáxias, mucuas, territórios, mocambolas, natures, tags, licenças |
sync.py | 32 | mucua local, Tor, chaves SSH, links, instalar galáxia, sync, descoberta, rede local |
auth.py | 11 | login, token, cadastro, desafio, identidade |
colecoes.py | 10 | coleções e histórias, membros, capa |
pedidos.py | 8 | pedidos e rotas (store-and-forward) |
historico.py, recovery.py | 6 + 6 | linha do tempo do git, restaurar; recuperação de identidade |
relacoes.py, xmpp.py, activitypub.py | 4 + 3 + 4 | relações; ponte XMPP; Fediverso (só com BBX_FED_AP=on) |
app/<lente>/… | — | rotas que cada aplicação registra |
Documentação viva: http://127.0.0.1:8000/docs.
SaberService por dentro
classDiagram
class SaberService {
-dict _repos «uuid → SaberRepository»
-dict _graphs
+galaxy_uuids() list
+repo(galaxia) SaberRepository
+search(… actor) Page
+get_saber(uuid, actor) dict
+create_saber / update_saber / delete_saber
+add_contents(uuid, files, actor)
+content_path / thumbnail_path (actor)
+get_collection / membros_dinamicos / add_collection_member …
+find_mocambola(username) (repo, saber)
+effective_role(saber) str
+galaxias_administradas(uuid) list
+historico_galaxia / restaurar
-_find_saber(uuid, galaxia)
-_descritor(…) dict
-_pos_escrita(repo, uuid)
}
class CopiasService
class PedidosService
class Operacoes {
+buscar / publicar / editar / deletar
}
class InProcessBackend
class HttpBackend
SaberService "1" o-- "*" SaberRepository
SaberService ..> CopiasService
SaberService ..> PedidosService
Operacoes --> InProcessBackend : CLI local / bot
Operacoes --> HttpBackend : bbx --api-url
InProcessBackend --> SaberService
SaberService é um "objeto deus": concentra muito. É o primeiro candidato a dividir quando a equipe tiver fôlego — ver exercício 4.
A API não pode travar
Uma mucua roda em hardware modesto. Lições que estão no código:
- Trabalho pesado sai do event loop. Upload copia em blocos no threadpool; sync, instalação e pedidos viram jobs (
services/jobs.py): a rota responde202com umjob_ide o front acompanhaGET /sync/jobs/{id}. - Leituras repetidas viram uma só (
cache_leitura.py, single-flight + TTL curto invalidado porindex.geracao()). - O vigia (
vigia_api.py) registra no log as requisições presas e a pilha das threads, antes que o healthcheck reinicie o serviço. - 503 com
Retry-Afterquando um arquivo está sendo buscado de outra mucua: o front espera e tenta de novo (fetchComEspera).
sequenceDiagram
actor P as Pessoa
participant A as POST /sync/instalar-galaxia
participant J as jobs
participant T as thread
P->>A: url, somente_leitura
A->>J: criar("instalar-galaxia")
A->>T: executar_em_thread(job)
A-->>P: 202 {id, acompanhar}
T->>T: clone → reindex → espelhar identidade
T->>J: progresso("clonando…", "reindexando…")
P->>J: GET /sync/jobs/{id} (até concluir)
A galáxia em .zip usa o mesmo molde: POST /galaxias/{uuid}/exportar é um job que grava o zip (e o resultado traz o link para baixar), e POST /sync/importar-galaxia recebe o arquivo no corpo e instala num job — o mesmo _ativar_galaxia_instalada (reindexar, espelhar identidade) da instalação por URL. Pela CLI: bbx exportar-galaxia e bbx importar-galaxia.
CLI e bot: uma só camada de operações
publica/operacoes.py implementa buscar/publicar/editar/deletar uma vez. A CLI e o bot XMPP chamam a mesma Operacoes, com dois "backends": in-process (fala direto com o SaberService) ou HTTP (bbx --api-url, fala com uma mucua remota).
Práticas
- Escreva uma rota nova
GET /saberes/{uuid}/resumoque devolva título, nature e número de anexos, respeitando a leitura. Useget_sabercomactor. Teste chamando a função direto, como os testes fazem. - Em
test_jobs_e_instalar_async.py, por que o job guarda o erro real dogit clone? - Rode
bbx buscar pesca --jsone depoisbbx buscar pesca --api-url http://127.0.0.1:8000/api/v3. Onde os dois caminhos se separam no código? - Abra um zip feito por
bbx exportar-galaxia. Por que ele leva umgit bundlee os anexos soltos, e não a pasta.gitinteira? (Dica: o que mora em.git/config?) E o queler_manifestorecusa? - Proponha como dividir
SaberServiceem três (leitura, escrita, coleções) sem quebrar as rotas. Que métodos privados seriam compartilhados?
Para conversar
- Por que uma operação que demora não deve segurar a requisição HTTP, quando quem está do outro lado tem internet ruim?
