2026-09-23·by Sijie Wang#lucerna#software#architecture

backends-three-services

The three-service backend split

Parent: key-designs

Three separately-deployable services, split by concern and language:

auth (Go/echo, ~2.7k LOC) — identity only. A standalone microservice wrapping Appwrite: POST /api/v1/auth/{login,register,logout,guest,recovery,recovery/reset}, GET/PUT/DELETE /me, and a cron guest-reaper (/cron/cleanup-guests, reaps guests >24 h). HttpOnly-cookie sessions (internal/cookie); the orchestrator composes Appwrite REST + a MainBackend user-purge call, so deleting an account cascades. Deliberately kept apart from the domain API.

mainbackend (Go/echo + pgx + google/wire, ~12.6k LOC) — the domain API + persistence tier. 15 route groups: books, bookmarks, notes, vocabulary (+ all-forms PATCH /:id/forms), reading_prefs, book_settings, catalog/catalog_bytes/covers, dictionary (the L2 shared word cache), review, client_logs, internal_purge, health. It is the server counterpart of the offline-first outbox (offline-first-outbox) — idempotent on duplicate writes so replayed outbox entries are safe — and it owns the SRS: the SM-2 scheduler lives here (internal/domain/review/srs.go), not on the client, so schedules are consistent across web/desktop and ungameable (vocabulary-loop).

nlp-api (Python/FastAPI + spaCy, ~3.6k LOC) — deterministic linguistics + heavy ingestion. The /tokenize, /lookup, /structure (dep-parse grammar), /translate engine (linguistic-two-engines) — plus the PDF/OCR ingestion routers (/parse-pdf, /ocr-images, Tesseract) that handle scanned books the client can't (upload-ingestion).

Why split this way: identity has different security/lifecycle needs than the domain (own service); the linguistics need Python's NLP ecosystem (spaCy, Tesseract, NLLB) so they can't live in the Go tier; and the Go domain API stays a clean, wire-DI'd persistence surface. The dependency-inversion in @lucerna/core (cross-platform-seams) means the shells see all three only as repository interfaces over HTTP.

(i18n note: the product ships 11 localesen, de, es, fr, ja, ko, nl, pt, zh, zh-HK — as full translation dictionaries in core/src/i18n/, consumed UI-agnostically through the core; mechanically simple but broad.)

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

backends-three-services