Airbnb (Distributed Booking)

A team-built Airbnb-style booking platform that grew from a monolith into event-driven microservices: a React/Redux frontend over four Node services and a Python AI agent, talking through Kafka with MongoDB, and deployable on Kubernetes.

ROLE
Developer
PERIOD
2025
DOMAIN
Distributed systems

OVERVIEW

Internally the team called it Voyage. A React/Redux/Vite frontend sits over four Node/Express services (traveler, owner, property, booking), each with its own MongoDB, communicating through Kafka topics with consumer groups and retry rather than direct calls. A separate Python FastAPI service runs a local Ollama model through LangChain to generate JSON travel itineraries, kept isolated from the booking path. The whole system ships with Docker Compose for local runs and a full set of Kubernetes manifests, plus Playwright, Postman, and JMeter for end-to-end, API, and load testing.

ARRIVED AS

“An Airbnb-style app has clearly separable concerns (travelers, owners, properties, bookings) that fight each other inside a single codebase as it grows. The job was to take a working monolith and re-architect it into independent services that communicate asynchronously, so a slow or failing service degrades the system instead of taking it down, and to make the whole thing deployable on Kubernetes.”

This was a team distributed-systems project built in two iterations: a monolithic first version (Lab 1) and then a re-architecture into event-driven microservices (Lab 2). The product is an Airbnb-style booking site with an added AI travel-planning concierge: travelers search and book properties; owners list and manage them. My focus was on the distributed back end: the services, their Kafka communication, and the deployment setup.

WHAT I BUILT

  1. 01A React + Redux + Vite frontend (Tailwind) with feature slices for auth, properties, bookings, favorites, and the owner dashboard, talking to the backend over a small API layer.
  2. 02Four Node/Express microservices, traveler, owner, property, and booking, each with its own responsibility and MongoDB data, deployable independently.
  3. 03Kafka as the event bus: the booking service consumes booking-requests and booking-updates topics with consumer groups and retry-on-failure, so booking status stays consistent across services without synchronous coupling.
  4. 04A separate Python FastAPI agent service that uses LangChain with a local Ollama model to generate day-by-day travel itineraries as structured JSON.
  5. 05Packaged for both local and cluster runs: Docker Compose for development and a full set of Kubernetes manifests, with Playwright end-to-end tests, a Postman collection, and JMeter load testing.

WHAT CHANGED

  • Re-architected from a Lab 1 monolith into a Lab 2 event-driven microservices system; the rewrite is the point: the same product, split up and made to survive failures.
  • The booking flow tolerates a missing Kafka broker (it logs and runs degraded) and retries failed messages, so async consistency does not become a single point of failure.
  • An AI concierge built as its own service rather than bolted onto the frontend, keeping the LLM dependency isolated behind a clean API.

ARCHITECTURE

The system is a React/Redux/Vite frontend over four Node/Express microservices, each owning a slice of the domain: traveler, owner, booking, and property. Each service has its own MongoDB data and exposes an HTTP API; cross-service workflows go through Kafka rather than direct calls. The booking service, for example, subscribes to booking-requests and booking-updates topics on a consumer group, so a booking made in one place propagates to the others asynchronously and survives transient failures through Kafka's retry. A separate Python FastAPI agent service wraps a local Ollama LLM through LangChain to generate travel itineraries, isolated so the AI dependency never touches the core booking path. Everything is containerized: Docker Compose for local development and a full set of Kubernetes manifests (services, MongoDB, Kafka, Ollama, config, secrets) for cluster deployment, with Playwright, Postman, and JMeter covering end-to-end, API, and load testing.

Data flow

click a stage

React/Redux pages call the service APIs through a small API layer; Redux slices hold auth, properties, bookings, favorites, and dashboard state.

COMPONENT

Frontend (React + Redux + Vite)

Property search, booking, favorites, and the owner dashboard; Redux slices for auth, properties, bookings, favorites, and dashboard state, styled with Tailwind.

ALSO IN THE BUILD

  • MongoDB

    Per-service document storage for users, properties, and bookings.

  • Docker Compose + Kubernetes manifests

    Local development via Compose and cluster deployment via a full manifest set (services, MongoDB, Kafka, Ollama, config, secrets), with Playwright, Postman, and JMeter testing.

Decisions, with the cost of each.

A decision without its trade-off is marketing. Each row says what was chosen, why, and what it gave up.

Communicate through Kafka instead of direct service-to-service calls

Synchronous calls between services couple their availability: if one is down, the caller fails. Routing booking events through Kafka topics with consumer groups means a service can be slow or briefly offline and catch up from the log, which is the whole reason to go microservices in the first place.

INSTEAD OF

Direct REST calls between services (tight coupling, cascading failures); a shared database (re-couples the services through their data).

Isolate the AI concierge as its own service

The LLM dependency (LangChain + a local Ollama model) is heavy and unrelated to booking correctness. Putting it behind its own FastAPI service keeps that dependency out of the core path, so the booking system does not inherit the AI service's footprint or failure modes.

INSTEAD OF

Call the LLM from the frontend or a core service (drags the model dependency into the booking path and the client bundle).

Degrade gracefully when Kafka is absent

A broker is not always available in local development. The booking service logs a warning and runs without Kafka rather than refusing to start, so developers can work on the rest of the system without standing up the full event bus.

INSTEAD OF

Hard-require Kafka at boot (blocks all local work on the service unless a broker is running).

The part that mattered.

The facts behind the work, and the code that produced them.

deployables
6 services
4 Node + 1 Python agent + React frontend
event bus
Kafka
booking topics, consumer groups, retry
full manifests
Kubernetes
+ Docker Compose, Playwright, JMeter
local LLM agent
Ollama
LangChain JSON itineraries
The booking service consumes Kafka topics, with retryjavascript
async function setupKafkaConsumer() {
  const consumer = await kafka.createConsumer('booking-status-sync-group');
  if (!consumer) {
    logger.warn('Kafka consumer not created - running without Kafka');
    return;   // degrade gracefully in local dev
  }

  await kafka.subscribe('booking-updates', async (updateData) => {
    // apply the status update to this service's MongoDB
  });

  await kafka.subscribe('booking-requests', async (bookingData) => {
    // create the booking; on error, rethrow to trigger Kafka retry
  });

  await kafka.startConsumer();
}

Cross-service booking consistency runs over two Kafka topics on a consumer group. Handlers rethrow on failure so Kafka retries the message, and if no broker is present the service logs and runs degraded instead of failing to start, the same degrade-gracefully approach used elsewhere in the project.

The AI concierge: LangChain + a local Ollama modelpython
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOllama(
    base_url=config.OLLAMA_API,
    model=config.OLLAMA_MODEL,
    temperature=0.2,   # lower temp -> more deterministic JSON
)

itinerary_prompt = ChatPromptTemplate.from_template("""
You are an expert travel agent. Create a day-by-day itinerary for {location}
for {duration} days given preferences: {preferences}.
Return strictly a JSON array, one object per day, no markdown.
""")

The travel concierge runs a local Ollama model through LangChain, with a low temperature and a strict JSON-only prompt so the itinerary parses reliably. Running the model locally keeps the feature free of an external API key, and isolating it in its own service keeps that choice from leaking into the rest of the system.

LEARNED

  1. Moving from a monolith to microservices is mostly a communication problem: the services are the easy part, deciding what goes over Kafka versus a direct call is where the design lives.

  2. Retry-on-failure plus a graceful no-broker path makes the event bus a resilience feature rather than a new single point of failure.

  3. Keeping the LLM in its own service paid off: the AI concierge could change models or break entirely without touching the booking flow.