StayEase is a Hotels.com-style hotel booking platform developed as a final project for SE 4458 — Software Architecture & Design of Modern Large Scale Systems.
The system is built on a microservices architecture with eight independently deployed services. All traffic enters through an Ocelot API Gateway which handles JWT validation via AWS Cognito before routing to downstream services. Hotel search, booking, comments, AI-assisted booking, and real-time notifications are each handled by a dedicated service. Relational data lives in Supabase (PostgreSQL), comments in MongoDB Atlas, and cache in Upstash Redis. An asynchronous booking notification pipeline runs over CloudAMQP RabbitMQ. A nightly AWS Lambda job monitors room capacity and alerts hotel administrators when occupancy exceeds 80% for the following month.
- Live URLs
- Architecture Overview
- Deployment
- Services
- Data Models
- Assumptions
- Issues Encountered
- Local Development
- Repository Structure
Note: The live deployment has been taken down. The services are no longer running.
The system follows a microservices architecture. All client requests are routed through a central API Gateway (Ocelot) where JWT tokens are validated. Downstream services communicate synchronously via REST and asynchronously via RabbitMQ.
Full diagram: docs/diagrams/microservices.md
graph TD
AdminClient[Admin Client]
Client[Client]
IAM[IAM<br>AWS Cognito]
Gateway[API Gateway<br>Ocelot]
HotelSvc[Hotel Service]
CommentsSvc[Comments Service]
AiAgent[AI Agent Service]
NotifSvc[Notification Service]
Lambda[Cron Jobs<br>AWS Lambda]
Redis[(Hotel Cache<br>Redis)]
Queue[(Queue<br>RabbitMQ)]
PgDB[(PostgreSQL<br>Supabase)]
MongoDB[(NoSQL DB<br>MongoDB Atlas)]
Resend[Resend<br>Email]
OpenAI[OpenAI<br>GPT-4o-mini]
EventBridge[EventBridge<br>nightly]
AdminClient -->|manage hotels and rooms| Gateway
Client -->|search hotel / book hotel| Gateway
Client -->|authenticate| IAM
AiAgent -->|search and book via tool calls| Gateway
Gateway -->|JWT validated here| HotelSvc
Gateway --> CommentsSvc
Gateway --> AiAgent
HotelSvc <-->|cache| Redis
HotelSvc --> PgDB
HotelSvc -->|new reservation| Queue
CommentsSvc --> MongoDB
Queue -->|consume| NotifSvc
NotifSvc --> Resend
NotifSvc --> PgDB
AiAgent --> OpenAI
EventBridge -->|nightly trigger| Lambda
Lambda --> PgDB
Lambda --> Resend
Note: The deployment has been taken down. The information below reflects the architecture as it was built and deployed.
Full CI/CD and deployment diagram: docs/diagrams/deploy_overall.md
| Service | Platform |
|---|---|
| client | Vercel |
| admin-client | Vercel |
| api-gateway | Azure Container Apps (external ingress) |
| hotel-service | Azure Container Apps (internal) |
| comments-service | Azure Container Apps (internal) |
| notification-service | Azure Container Apps (internal) |
| ai-agent-service | Azure Container Apps (internal) |
| cron-jobs | AWS Lambda + EventBridge |
CI/CD is handled by GitHub Actions with path-filtered workflows — a change to one service only redeploys that service. The test job (xUnit) gates the build step on every push to master.
flowchart LR
Dev[Developer<br>git push to master]
GH[GitHub Actions<br>path-filtered workflows]
Tests[dotnet test<br>xUnit]
GHCR[GHCR<br>Container Registry]
ACA[Azure Container Apps<br>Germany West Central]
Lambda[AWS Lambda<br>us-east-1]
Vercel[Vercel<br>auto-deploy]
Dev -->|push| GH
GH -->|test job| Tests
Tests -->|pass — gates build| GH
GH -->|docker build + push| GHCR
GH -->|az containerapp update| ACA
GH -->|dotnet lambda deploy-function| Lambda
GH -->|triggers| Vercel
GHCR -->|pull on deploy| ACA
Full service relationship diagram: docs/diagrams/microservices.md
| Service | Responsibility |
|---|---|
api-gateway |
Ocelot reverse proxy — JWT validation, rate limiting, single entry point for all traffic |
hotel-service |
Hotel and room admin CRUD, search with Redis cache and 15% member discount, booking engine (SELECT FOR UPDATE), in-app notifications |
comments-service |
Hotel ratings and comments stored exclusively in MongoDB Atlas |
notification-service |
RabbitMQ consumer — sends booking confirmation emails via Resend and writes in-app notification rows |
ai-agent-service |
GPT-4o-mini orchestration with tool calling to hotel-service Search and Book APIs |
cron-jobs |
AWS Lambda triggered nightly by EventBridge — checks room capacity below 20% for next month and alerts hotel admins |
client |
User-facing Next.js app — search, book, AI chat, comments, notifications, My Bookings |
admin-client |
Admin Next.js panel — hotel and room management, availability windows, image gallery, reservations, notifications |
Full schema with all fields and assumptions: docs/4_Data_Models.md
erDiagram
Hotels {
UUID Id PK
string Name
string LocationPoint
text Description
string AdminEmail
string AdminSub "nullable"
string ImageUrl "nullable"
double Latitude "nullable"
double Longitude "nullable"
}
Rooms {
UUID Id PK
UUID HotelId FK
string RoomType
decimal BasePrice
}
RoomAvailability {
UUID Id PK
UUID RoomId FK
date StartDate
date EndDate
boolean IsVacant
int TotalCapacity
int ReservedCount
}
Reservations {
UUID Id PK
UUID RoomId FK
string UserId
date CheckInDate
date CheckOutDate
int GuestCount
decimal PricePaid
}
Notifications {
UUID Id PK
string UserId
string Title
text Body
boolean IsRead
timestamp CreatedAt
}
HotelImages {
UUID Id PK
UUID HotelId FK
string Title
string ImageUrl
timestamp CreatedAt
}
Hotels ||--o{ Rooms : "has"
Rooms ||--o{ RoomAvailability : "has"
Rooms ||--o{ Reservations : "has"
Hotels ||--o{ HotelImages : "has"
erDiagram
HotelComments {
ObjectId _id PK
UUID hotelId "ref to Supabase Hotels"
string userId "Cognito sub"
date travelDate
float overallRating
object categoryRatings
string commentText
string adminReply "nullable"
timestamp createdAt
}
categoryRatings is an embedded object with fields: cleanliness, staff, facilities, ecoFriendly.
Full assumptions: docs/5_Assumptions.md
LocationPointis a plain text field used for keyword search (LIKE '%Istanbul%'). It is not parsed as coordinates.LatitudeandLongitudeare separate nullable columns used only for map pins.- The 15% member discount is applied server-side whenever a valid Cognito JWT is present on the search request.
SELECT FOR UPDATEon theRoomAvailabilityrow is the sole concurrency guard for booking. There is no payment gateway — bookings are confirmed immediately on commit.PricePaidis captured at booking time and is independent of futureBasePricechanges.- The Cognito
subclaim is used as the canonical user identifier across all services. - JWT validation happens exclusively at the Ocelot API Gateway. Downstream services trust forwarded headers.
- Any authenticated user can post a comment. There is no verified-guest restriction.
- The nightly Lambda job checks capacity for the next calendar month. Alert threshold is less than 20% remaining capacity.
- The AI agent is stateless — no chat history is persisted between sessions.
- There is no booking cancellation flow.
Full issue log: docs/6_Issues_Encountered.md
- AWS Lambda does not support .NET 9 — upgraded
cron-jobstonet10.0. - .NET JWT middleware remaps the
subclaim by default — fixed withMapInboundClaims = falseonAddJwtBearer. - Frontend was sending the access token instead of the ID token — the access token does not carry the
emailclaim, causing downstream rejections. - Queue name mismatch between publisher (
booking-events) and consumer (booking.events) silently dropped all booking notifications. - RabbitMQ consumer crashed on email failure and requeued messages indefinitely — isolated the email call in a try-catch so the message is always acknowledged.
- Supabase direct connection requires IPv6 — switched to the session pooler URL for IPv4 compatibility.
- Upstash Redis removed its free tier mid-project — migrated to Redis Cloud with a connection string format change.
- Initial deployment target was Google Cloud Run — switched to Azure Container Apps for better internal service networking.
Prerequisites: Docker, .NET 9 SDK, Node.js 22
# Start local infrastructure (Postgres, MongoDB, Redis, RabbitMQ)
docker-compose up
# Run a .NET service
cd src/hotel-service
dotnet run
# Run the user client
cd src/client
npm install && npm run dev
# Run the admin client
cd src/admin-client
npm install && npm run dev -- --port 3001
# Run tests
cd src/hotel-service-tests
dotnet testEach service reads configuration from appsettings.Development.json (gitignored). Copy .env.example to .env and fill in credentials before running docker-compose up.
/src
/api-gateway .NET 9 Ocelot reverse proxy
/hotel-service .NET 9 Web API — Admin, Search, Booking
/hotel-service-tests xUnit tests
/comments-service .NET 9 Web API — MongoDB only
/comments-service-tests xUnit tests
/notification-service .NET 9 Web API — RabbitMQ consumer + Resend email
/notification-service-tests xUnit tests
/ai-agent-service .NET 9 Web API — OpenAI GPT-4o-mini orchestration
/ai-agent-service-tests xUnit tests
/cron-jobs AWS Lambda (.NET 10) — nightly capacity checker
/client Next.js — user app
/admin-client Next.js — admin panel
/.github/workflows Per-service CI/CD with path filtering
/docs Architecture, data models, assumptions, issues