AI Tutor
An event-driven microservices system that teaches.
- Microservices
- 5
- Event broker
- RabbitMQ
- Architecture
- Hexagonal + CQRS
- Vector store
- Qdrant
The brief
Build an AI tutor that can read a textbook, do its own web research when the textbook is wrong or stale, and adapt its teaching style to a specific learner — without melting under the cost of running an LLM behind every keystroke.
This was a deliberate over-engineering exercise: distributed systems patterns applied to a problem that almost needed them. Some patterns paid off. Some I’d cut today.
Architecture
5 services, RabbitMQ for events, Postgres for state, Qdrant for vectors. The cross-service contract is the outbox pattern — every state change writes to the DB and an outbox row in the same transaction; an outbox worker publishes events. No lost messages, no two-phase commit.
flowchart LR
Frontend["Frontend<br/>Next.js + API gateway"]
Tutor["Tutor Service<br/>FastAPI"]
Doc["Document Service<br/>FastAPI"]
Research["Research Service<br/>FastAPI"]
Knowledge["Knowledge Service<br/>FastAPI"]
MQ([RabbitMQ])
Outbox[("Outbox table")]
Worker["Outbox worker"]
PG[("Postgres")]
Qdrant[("Qdrant")]
Frontend -->|HTTP| Tutor
Tutor --> PG
Tutor -.write.-> Outbox
Outbox --> Worker
Worker -->|publish| MQ
MQ --> Doc
MQ --> Research
MQ --> Knowledge
Doc -->|chunks| Knowledge
Research -->|sources| Knowledge
Knowledge --> Qdrant
The services
- Frontend Service (Next.js + TypeScript) — UI and API gateway
- Tutor Service (FastAPI) — core lesson logic and LLM orchestration
- Document Service (FastAPI) — book and PDF ingestion, chunking, normalisation
- Research Service (FastAPI) — web search, content extraction, freshness checks
- Knowledge Service (FastAPI) — Qdrant vector store and semantic retrieval
Patterns I committed to
- Hexagonal architecture in every service — domain at the centre, ports for I/O, adapters for the messy real world. Made testing a joy and integration changes painless.
- CQRS where reads and writes had genuinely different shapes (lessons, progress, recommendations). Skipped it everywhere else.
- Outbox pattern for cross-service consistency.
- Saga-style coordination for multi-service flows like “ingest a book and seed lessons.”
The outbox in code
Same transaction, two writes — the lesson row and the outgoing event. The worker is the only thing publishing to RabbitMQ. The service never talks to the broker directly.
# tutor-service/app/services/lessons.py
async def create_lesson(db: AsyncSession, payload: LessonCreate) -> Lesson:
async with db.begin():
lesson = Lesson(**payload.model_dump())
db.add(lesson)
# Same transaction: append the domain event to the outbox.
# The outbox worker will publish it to RabbitMQ and mark it sent.
db.add(OutboxEvent(
aggregate_id=lesson.id,
event_type="LessonCreated",
payload=lesson.to_event(),
))
return lesson
What I learned
- Microservices are a tax most products can’t afford. This system is a teaching tool I built for myself. In hindsight, three of the five services should have been modules in a monolith. The two that actually benefit from being separate are Document (heavy, batch, spiky) and Knowledge (vector queries, cache-heavy).
- The outbox pattern is the single highest-ROI distributed-systems primitive. I’d reach for it again on the first cross-service write. Everything else is negotiable.
- Vector search is a footgun without rerankers. Top-k cosine retrieval with a 768-dim embedding gets ~70% of the right answer. Adding a cross-encoder reranker on top moved that to ~92% on the same queries. The user-perceived quality jump was massive.
- Cost lives in the document service. Embedding a 400-page textbook is the most expensive hour the system has. Caching aggressively, deduping by hash, and embedding chunks lazily on first retrieval cut that cost by ~80%.
Private repo. Architecture decisions, event schemas, and the outbox implementation are available for review under NDA.
Want this for your business?
Let's discuss your AI build.
I do strategy calls, architecture audits, and full pilot builds. Same depth you just read about — for your product.