← All work
● Private / 2024–2025 / Founder & engineer

AI Tutor

An event-driven microservices system that teaches.

Python 3.11 FastAPI RabbitMQ Postgres Qdrant LangChain OpenAI Next.js Docker Compose Hexagonal Architecture CQRS Outbox Pattern
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.