Parte III — Ramas, savia y semillas
06 · Las ramas: servicios, API y CLI
Del tronco salen pocas ramas gruesas, y de ellas muchas finas. En Baobáxia: un servicio grande (
SaberService) y muchos servicios pequeños a su alrededor.
Archivos para leer
services/saber_service.py—SaberService(≈ 4000 líneas: léelo por secciones; los comentarios# ==== ESCRITA: … ====son el índice).api/v3/app.py— el armado de la aplicación, middlewares, manejadores de error.api/v3/deps.py— el servicio global y elmocde cada petición.- Una ruta 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— la misma operación sirviendo a la CLI y al bot.applications/— las lentes (acervo, blog, mapa, timeline, agenda).
El mapa de la API
| Router | Rutas | Para qué |
|---|---|---|
saberes.py | 35 | CRUD, anexos, miniatura, subtítulos, texto, vista previa, copiar/mover, protección |
estrutura.py | 32 | galaxias, mucuas, territorios, mocambolas, natures, tags, licencias |
sync.py | 32 | mucua local, Tor, claves SSH, enlaces, instalar galaxia, sync, descubrimiento, red local |
auth.py | 11 | login, token, registro, desafío, identidad |
colecoes.py | 10 | colecciones e historias, miembros, portada |
pedidos.py | 8 | pedidos y rutas (store-and-forward) |
historico.py, recovery.py | 6 + 6 | línea de tiempo de git, restaurar; recuperación de identidad |
relacoes.py, xmpp.py, activitypub.py | 4 + 3 + 4 | relaciones; puente XMPP; Fediverso (solo con BBX_FED_AP=on) |
app/<lente>/… | — | rutas que registra cada aplicación |
Documentación 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 es un "objeto dios": concentra mucho. Es el primer candidato a dividir cuando el equipo tenga aliento — ver el ejercicio 5.
La API no puede trabarse
Una mucua corre en hardware modesto. Lecciones que están en el código:
- El trabajo pesado sale del event loop. La subida copia en bloques en el threadpool; sync, instalación y pedidos se vuelven jobs (
services/jobs.py): la ruta responde202con unjob_idy el front sigueGET /sync/jobs/{id}. - Lecturas repetidas se vuelven una sola (
cache_leitura.py, single-flight + TTL corto invalidado porindex.geracao()). - El vigía (
vigia_api.py) registra en el log las peticiones trabadas y la pila de los hilos, antes de que el healthcheck reinicie el servicio. - 503 con
Retry-Aftercuando se está trayendo un archivo de otra mucua: el front espera y lo intenta de nuevo (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 → reflejar identidad
T->>J: progreso("clonando…", "reindexando…")
P->>J: GET /sync/jobs/{id} (hasta terminar)
La galaxia en .zip usa el mismo molde: POST /galaxias/{uuid}/exportar es un job que graba el zip (y el resultado trae el enlace para descargarlo), y POST /sync/importar-galaxia recibe el archivo en el cuerpo y lo instala en un job — el mismo _ativar_galaxia_instalada (reindexar, reflejar identidad) de la instalación por URL. Por la CLI: bbx exportar-galaxia y bbx importar-galaxia.
CLI y bot: una sola capa de operaciones
publica/operacoes.py implementa buscar/publicar/editar/borrar una vez. La CLI y el bot XMPP llaman a la misma Operacoes, con dos "backends": in-process (habla directo con SaberService) o HTTP (bbx --api-url, habla con una mucua remota).
Prácticas
- Escribe una ruta nueva
GET /saberes/{uuid}/resumoque devuelva título, nature y número de anexos, respetando la lectura. Usaget_saberconactor. Pruébala llamando a la función directamente, como hacen los tests. - En
test_jobs_e_instalar_async.py, ¿por qué el job guarda el error real delgit clone? - Corre
bbx buscar pesca --jsony despuésbbx buscar pesca --api-url http://127.0.0.1:8000/api/v3. ¿Dónde se separan los dos caminos en el código? - Abre un zip hecho con
bbx exportar-galaxia. ¿Por qué lleva ungit bundley los anexos sueltos, y no la carpeta.gitentera? (Pista: ¿qué vive en.git/config?) ¿Y qué rechazaler_manifesto? - Propón cómo dividir
SaberServiceen tres (lectura, escritura, colecciones) sin romper las rutas. ¿Qué métodos privados se compartirían?
Para conversar
- ¿Por qué una operación que tarda no debe retener la petición HTTP, cuando quien está del otro lado tiene mala conexión?
