Parte III — Rami, linfa e semi
06 · I rami: servizi, API e CLI
Dal tronco escono pochi rami grossi, e da questi molti sottili. In Baobáxia: un servizio grande (
SaberService) e molti servizi piccoli intorno a lui.
File da leggere
services/saber_service.py—SaberService(≈ 4000 righe: leggilo per sezioni; i commenti# ==== ESCRITA: … ====sono l'indice).api/v3/app.py— l'assemblaggio dell'applicazione, middleware, gestori di errore.api/v3/deps.py— il servizio globale e ilmocdi ogni richiesta.- Una rotta di ogni 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— la stessa operazione al servizio della CLI e del bot.applications/— le lenti (archivio, blog, mappa, timeline, agenda).
La mappa dell'API
| Router | Rotte | A cosa serve |
|---|---|---|
saberes.py | 35 | CRUD, allegati, miniature, sottotitoli, testo, anteprima, copia/sposta, protezione |
estrutura.py | 32 | galassie, mucua, territori, mocambola, nature, tag, licenze |
sync.py | 32 | mucua locale, Tor, chiavi SSH, link, installare galassie, sync, scoperta, rete locale |
auth.py | 11 | login, token, registrazione, sfida, identità |
colecoes.py | 10 | collezioni e storie, membri, copertina |
pedidos.py | 8 | richieste e rotte (store-and-forward) |
historico.py, recovery.py | 6 + 6 | timeline di git, ripristino; recupero dell'identità |
relacoes.py, xmpp.py, activitypub.py | 4 + 3 + 4 | relazioni; ponte XMPP; Fediverso (solo con BBX_FED_AP=on) |
app/<lente>/… | — | rotte che ogni applicazione registra |
Documentazione viva: http://127.0.0.1:8000/docs.
Dentro SaberService
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 è un "oggetto dio": concentra molto. È il primo candidato da dividere quando la squadra avrà energie — vedi l'esercizio 5.
L'API non deve bloccarsi
Una mucua gira su hardware modesto. Lezioni che stanno nel codice:
- Il lavoro pesante esce dall'event loop. L'upload copia a blocchi nel threadpool; sync, installazioni e richieste diventano job (
services/jobs.py): la rotta risponde202con unjob_ide il front segueGET /sync/jobs/{id}. - Letture ripetute diventano una sola (
cache_leitura.py, single-flight + TTL breve invalidato daindex.geracao()). - La sentinella (
vigia_api.py) registra nel log le richieste bloccate e lo stack dei thread, prima che l'healthcheck riavvii il servizio. - 503 con
Retry-Afterquando un file viene preso da un'altra mucua: il front aspetta e riprova (fetchComEspera).
sequenceDiagram
actor P as Persona
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 → rispecchiare identità
T->>J: avanzamento("clonazione…", "reindicizzazione…")
P->>J: GET /sync/jobs/{id} (fino alla fine)
La galassia in .zip usa lo stesso stampo: POST /galaxias/{uuid}/exportar è un job che scrive lo zip (e il risultato porta il link per scaricarlo), e POST /sync/importar-galaxia riceve il file nel corpo e lo installa in un job — lo stesso _ativar_galaxia_instalada (reindicizzare, rispecchiare l'identità) dell'installazione da URL. Dalla CLI: bbx exportar-galaxia e bbx importar-galaxia.
CLI e bot: un solo strato di operazioni
publica/operacoes.py implementa cerca/pubblica/modifica/cancella una volta sola. La CLI e il bot XMPP chiamano la stessa Operacoes, con due "backend": in-process (parla direttamente con SaberService) o HTTP (bbx --api-url, parla con una mucua remota).
Esercizi
- Scrivi una rotta nuova
GET /saberes/{uuid}/resumoche restituisca titolo, nature e numero di allegati, rispettando i permessi di lettura. Usaget_saberconactor. Provala chiamando la funzione direttamente, come fanno i test. - In
test_jobs_e_instalar_async.py, perché il job conserva l'errore reale digit clone? - Esegui
bbx buscar pesca --jsone poibbx buscar pesca --api-url http://127.0.0.1:8000/api/v3. Dove si separano le due strade nel codice? - Apri uno zip fatto da
bbx exportar-galaxia. Perché contiene ungit bundlee gli allegati sciolti, e non l'intera cartella.git? (Suggerimento: cosa vive in.git/config?) E cosa rifiutaler_manifesto? - Proponi come dividere
SaberServicein tre (lettura, scrittura, collezioni) senza rompere le rotte. Quali metodi privati sarebbero condivisi?
Da discutere
- Perché un'operazione lenta non deve trattenere la richiesta HTTP, quando chi sta dall'altra parte ha una connessione scadente?
