Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<h1 align="center">LOVE.NET</h1>
<p align="center"> ReactJS SPA dating application — now running on event-driven .NET 8 microservices
<p align="center"> React SPA dating application — now running on event-driven .NET 8 microservices
<br>
</p>

## 📝 Table of Contents
- [About](#about)
- [Architecture](#architecture)
- [How the Components Communicate](#communication)
- [Getting Started](#getting_started)
- [Database Placement](#database_placement)
- [Tests](#tests)
Expand All @@ -15,7 +16,7 @@
`LOVE.NET` gives you opportunity to match with people around the world and chat easily in real time.
Some accounts, countries, cities, genders are loaded after the first start of the application — through the same Kafka event pipeline production traffic uses.

This repository was refactored from a layered monolith into seven services built to scale the realtime chat workload horizontally. The original monolith is preserved on the [`monolith-snapshot`](../../tree/monolith-snapshot) branch.
This repository is a **refactoring test driven by Claude Fable 5** (Anthropic's Claude Code): the original layered monolith was rebuilt into seven event-driven services designed to scale the realtime chat workload horizontally, and the SPA was rewritten on the 2026 React stack. The pre-refactor monolith is preserved on the [`monolith-snapshot`](../../tree/monolith-snapshot) branch.

## 🏗️ Architecture <a name = "architecture"></a>

Expand All @@ -35,6 +36,31 @@ React SPA ──► Gateway (YARP) ──► Identity │ Profile │ Matching

The full design — topics, delivery semantics, resiliency patterns, scaling story — is in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).

## 🔌 How the Components Communicate <a name = "communication"></a>

**SPA → Gateway (HTTP + WebSocket).** The React app talks only to the YARP gateway on `:8080`: REST under `/api/*` with a JWT bearer header (silent refresh via an HttpOnly cookie), and one SignalR WebSocket at `/chat` (the JWT rides the `access_token` query parameter). The gateway proxies both, applies CORS and per-IP rate limits, and serves `/api/admin/dashboard` itself by fanning out to each service's `/internal/stats` with timeouts, retries and circuit breakers.

**Synchronous (HTTP).** That gateway fan-out is the *only* service-to-service HTTP in the system — services never call each other for business flows, so no service can stall another.

**Asynchronous (Kafka).** Everything cross-service is an event. Producers write to a transactional **outbox** table in the same DB transaction as their state change; a relay publishes to Kafka; consumers dedupe via an **inbox** table (at-least-once delivery, effectively-once processing) and dead-letter poison messages after bounded retries. Keys guarantee per-user / per-room ordering:

| Topic | Producer → Consumers | Purpose |
|---|---|---|
| `identity.user-registered` | Identity → Profile, Notifications | build the profile aggregate, contacts |
| `identity.user-banned` | Identity → Profile | mirror moderation state |
| `profile.user-updated` (enriched) | Profile → Matching, Chat, Identity, Notifications | read models: swipe deck, chat avatars, login response |
| `matching.match-created` | Matching → Chat, Realtime, Notifications | provision the room, push the match toast, send emails |
| `chat.message-sent` (8 partitions, key = roomId) | Realtime → Chat | durable persistence of hub messages |
| `chat.room-read` | Realtime → Chat | read-receipt watermarks |
| `chat.room-provisioned` | Chat → Realtime | public-room join authorization |
| `notifications.email-requested` | Identity → Notifications | verification / reset emails |

**Redis (Realtime only).** The SignalR hub is stateless: the **Redis backplane** fans `ReceiveMessage`/`MatchReceived` broadcasts across hub replicas, room presence lives in Redis hashes, and the public-room set in a Redis set — so any number of Realtime instances behave as one. Realtime has no database; it publishes straight to Kafka (acks=all) *before* broadcasting, so no client ever sees a message the broker didn't accept.

**PostgreSQL (database-per-service).** Each service owns its schema and is the only writer to it; what other services need arrives as events and lands in local read models — there are no cross-service joins or shared tables.

**External services.** Identity/Profile/Chat upload images to **Cloudinary** over HTTPS; Notifications delivers mail through **SendGrid**. Auth everywhere is the same HS512 **JWT** issued by Identity and validated locally by every service and the hub — no auth round-trips.

## 🏁 Getting Started <a name = "getting_started"></a>

### Run the project
Expand All @@ -49,7 +75,7 @@ docker compose up -d --build
SPA: http://localhost:3000 · Gateway: http://localhost:8080 · Swagger: http://localhost:8081..8086/swagger
```
```
./scripts/smoke-test.ps1 (20-step end-to-end proof of the whole stack)
./scripts/smoke-test.ps1 (25-step end-to-end proof of the whole stack)
```

![Containers](docs/images/containers.png)
Expand Down Expand Up @@ -132,12 +158,14 @@ Every service owns its schema; the shared tables you see (`outbox_messages`, `in
![Database placement](docs/images/database-placement.png)

## 🧪 Tests <a name = "tests"></a>
* 37 NUnit tests across the five services (ported and adapted from the monolith suite)
* Plus a 20-step end-to-end smoke test against the live compose stack (`scripts/smoke-test.ps1`)
* 41 NUnit tests across the five services (ported and adapted from the monolith suite)
* Plus a 25-step end-to-end smoke test against the live compose stack (`scripts/smoke-test.ps1`)
* ![Tests](docs/images/tests.png)

## ⛏️ Built Using <a name = "built_using"></a>
* ReactJS
* React 19 + TypeScript (Vite)
* Tailwind CSS v4 + shadcn/ui
* TanStack Query + motion + sonner
* .NET 8 APIs (7 services)
* EF Core 8 with PostgreSQL (database-per-service)
* Apache Kafka (KRaft)
Expand All @@ -152,7 +180,5 @@ Every service owns its schema; the shared tables you see (`outbox_messages`, `in
* NUnit
* Moq
* Axios
* React Bootstrap
* MomentJS
* React Tinder Card
* Day.js
* Docker Compose
1 change: 0 additions & 1 deletion Web-FrontEnd/.dockerignore

This file was deleted.

23 changes: 0 additions & 23 deletions Web-FrontEnd/.gitignore

This file was deleted.

13 changes: 0 additions & 13 deletions Web-FrontEnd/Dockerfile

This file was deleted.

70 changes: 0 additions & 70 deletions Web-FrontEnd/README.md

This file was deleted.

Loading