Skip to main content

Understanding the Architecture

This document walks you through Bellamy Book’s overall architecture so you can see how all the pieces fit together. It is the recommended starting point for developers and operators who need a clear mental model of the system.


What is Bellamy Book?

Bellamy Book is a social networking application with:

  • A social frontend (React + Vite) for users: feed, posts, comments, reactions, stories, messaging, blogs, hashtags, notifications, friends.
  • An admin panel (React + CoreUI) for administrators: dashboard, users, content/blogs, notifications, support tickets, backup, license, AI agent, email campaigns.
  • A single Backend API (ASP.NET Core) that serves both frontend and admin via REST (and real-time via SignalR).
  • Background workers that consume events from Kafka (and, for AI blog generation, from RabbitMQ) to do async processing.
  • Scheduled (cron) jobs that run inside the API process (Quartz) and inside the BlogAutoGenerationWorker.
  • Multiple data stores: PostgreSQL (core data), MongoDB (stories, chat, notifications), Redis (cache, sessions), Neo4j (social graph), Elasticsearch (full-text search).

The architecture is modular but not “microservices” in the sense of many small APIs: there is one API, many workers, and polyglot persistence.


High-Level Picture

Everything starts with clients (browser or admin app) talking to the Backend API. The API reads and writes databases and publishes events to Kafka. Workers consume those events and update databases (and sometimes push real-time updates). Scheduled jobs run on a timer inside the API (and one inside the BlogAutoGenerationWorker). One special flow uses RabbitMQ for AI blog generation.

┌─────────────────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Social Frontend │ │ Admin Panel │ │
│ │ (React + Vite) │ │ (React + CoreUI) │ │
│ └────────┬─────────┘ └────────┬────────┘ │
└───────────┼────────────────────────────────────────┼──────────────────────────────┘
│ HTTP/REST (+ WebSocket/SignalR) │ HTTP/REST
▼ ▼
┌───────────────────────────────────────────────────────────────────────────────────┐
│ BACKEND API (ASP.NET Core) │
│ Controllers: Authentication, Post, Comment, Story, Chat, Notification, Blog, │
│ Friendship, Hashtags, Search, Admin, License, AiAgent, Ticket, Media, SEO... │
│ + Quartz scheduled jobs (refresh token cleanup, user sync, Elasticsearch sync, │
│ story cleanup, birthday notifications, email campaigns, poll expiration, │
│ license validation, scheduled backup) │
└───┬─────────────┬─────────────┬─────────────┬─────────────┬─────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌─────────────┐
│Postgre│ │MongoDB│ │ Redis │ │ Neo4j │ │Elasticsearch│
│ SQL │ │ │ │ │ │ │ │ OpenSearch │
└───────┘ └───────┘ └───────┘ └───────┘ └─────────────┘
▲ ▲ ▲ ▲ ▲
│ │ │ │ │
│ ┌──────┴─────────┴─────────────┴─────────────┴──────┐
│ │ KAFKA (event streaming) │
│ └──────┬─────────┬─────────┬─────────┬──────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────────────────────────┐
│ WORKERS (separate processes): GraphWorker, ScoringWorker, TrendingWorker, │
│ HashtagWorker, ElasticsearchSyncWorker, InteractionWorker, MediaProcessingWorker,│
│ WebSocketWorker, WebPushNotificationWorker, ChatWorker │
└───────────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────────────────────────────┐
│ BlogAutoGenerationWorker (separate process): consumes RabbitMQ for AI blog │
│ generation; has its own Quartz job (BlogGenerationJob) for scheduled generation │
└───────────────────────────────────────────────────────────────────────────────────┘

Takeaway: One API, many data stores, Kafka for most async work, RabbitMQ + one worker for AI blogs, and scheduled jobs inside the API (and inside the blog worker).


How a Request Flows (Synchronous Path)

When a user or admin does something in the UI, the flow is:

  1. Client (frontend or admin) sends an HTTP request (e.g. POST /api/Post/users/{userId} to create a post) with a JWT in the header.
  2. Backend API receives the request:
    • Middleware runs: error handling, host validation, license enforcement (some routes require a valid license/feature flag), rate limiting, then authentication (JWT) and authorization (roles: User, Moderator, Admin).
  3. Controller (e.g. PostController) receives the request, maps the body to a DTO, and sends a command or query via MediatR to the Application layer.
  4. Application (handlers) runs business logic: load/validate entities, update Domain objects, call Infrastructure (repositories) to read/write PostgreSQL or MongoDB, and optionally use Redis (cache) or call external services.
  5. For write operations, the handler often publishes one or more events to Kafka (e.g. “post created”, “comment added”). The API does not wait for workers; it returns as soon as the primary write and event publish succeed.
  6. Response is returned to the client (e.g. 201 Created with the new post).

Real-time: For live updates (notifications, chat), the client also maintains a WebSocket/SignalR connection to the API. Workers or the API push messages through that connection; the flow is described in Data Flow.

Takeaway: The synchronous path is “client → API → databases (+ optional Kafka publish)”. Workers are not in the critical path of a single request.


Data Stores: Who Stores What?

Bellamy Book uses polyglot persistence: the right store for the right kind of data.

StoreUsed forWritten by
PostgreSQLUsers, posts, comments, reactions, friendships, follows, hashtags, blogs, tickets, email campaigns, license, AI agent settings, etc.API (primary); many workers (scores, trends, graph-related, etc.)
MongoDBStories, chat messages, notifications (document-shaped, flexible schema)API and workers (e.g. WebSocketWorker, ChatWorker)
RedisCache (user, post, feed, story), sessions, rate limiting, real-time stateAPI and workers
Neo4jSocial graph (friends, suggestions, recommendations)GraphWorker, ScoringWorker (driven by Kafka events from API)
Elasticsearch/OpenSearchFull-text search (posts, blogs)ElasticsearchSyncWorker (syncs from PostgreSQL after events)

The API is the main writer for PostgreSQL and MongoDB for user-facing actions; workers keep Neo4j, Elasticsearch, scores, and trends in sync and write back to PostgreSQL/MongoDB/Redis where needed.

Details: Database Architecture.


Event Flow: What Happens After the API Publishes?

Many user actions cause the API to publish events to Kafka (e.g. post created, comment added, reaction added). Those events are consumed by workers that run in separate processes:

  1. GraphWorker — Updates Neo4j (social graph, friend suggestions).
  2. ScoringWorker — Updates content scores, relevance, rankings (PostgreSQL/Neo4j).
  3. TrendingWorker — Updates trending content (PostgreSQL).
  4. HashtagWorker — Extracts and indexes hashtags from content (PostgreSQL).
  5. ElasticsearchSyncWorker — Syncs posts/blogs (and optionally users) to Elasticsearch for search.
  6. InteractionWorker — Processes engagement and interaction metrics.
  7. MediaProcessingWorker — Processes media (images, video) and writes to MongoDB/storage.
  8. WebSocketWorker — Pushes real-time notifications to connected clients (uses Redis for connection state).
  9. WebPushNotificationWorker — Sends browser push notifications.
  10. ChatWorker — Real-time chat (SignalR hub), online presence, offline messages (MongoDB).

So: API writes primary data and publishes to Kafka → Workers consume and update other stores (Neo4j, Elasticsearch, Redis, MongoDB) or trigger side effects (notifications, media processing).

One exception: AI blog generation does not use Kafka. The API (or admin) sends a message to RabbitMQ; the BlogAutoGenerationWorker consumes it, generates the blog (AI, research, images, SEO), and writes the result to PostgreSQL. That worker also has a scheduled Quartz job (BlogGenerationJob) that runs on a cron (e.g. daily) to generate blogs automatically.

Details: Data Flow, Workers, Microservices.


Scheduled Jobs (Cron)

Besides Kafka-driven workers, the system uses scheduled jobs that run on a timer:

  • Inside the API process (Quartz):
    Refresh token cleanup, user sync (PostgreSQL → Neo4j), Elasticsearch catch-up sync, temp file cleanup, story cleanup, birthday notifications, email campaign sending, poll expiration, license validation, scheduled backup.
    Schedules are configured via Quartz:{JobName} in config; backup schedule can also be set in Admin → Backup.

  • Inside BlogAutoGenerationWorker (Quartz):
    BlogGenerationJob — runs on a cron (e.g. daily at 2 AM) when AI blog generation is enabled; schedule is stored in the database (AI Agent settings) and can be changed from the admin panel.

So: two places run cron jobs — the main API (most of them) and the BlogAutoGenerationWorker (blog generation only).

Details: Scheduled Jobs.


Deployment: What Runs Where?

  • Frontend / Admin — Static builds (e.g. served by Nginx or a CDN). Two separate apps: social frontend and admin panel.
  • Backend API — One or more instances (e.g. Docker). Contains REST, SignalR, and all Quartz jobs listed above (same process).
  • Workers — Each worker type is a separate deployable (own container/pod): GraphWorker, ScoringWorker, TrendingWorker, HashtagWorker, ElasticsearchSyncWorker, InteractionWorker, MediaProcessingWorker, WebSocketWorker, WebPushNotificationWorker, ChatWorker. They need Kafka (and the same DB/config as the API where they write).
  • BlogAutoGenerationWorker — Separate service; needs RabbitMQ, API database (for settings/blogs), and AI provider config.
  • Databases — PostgreSQL, MongoDB, Redis, Neo4j, Elasticsearch/OpenSearch; typically separate containers or managed services.
  • Message brokers — Kafka (events), RabbitMQ (AI blog generation only).

Details: Deployment, Scaling.


Security and Licensing

  • Authentication: JWT access tokens + refresh tokens; optional OAuth (Google, etc.).
  • Authorization: Roles (User, Moderator, Admin). Admin-only routes (e.g. api/admin/*, api/EmailCampaign, api/ai-agent, api/tickets) are restricted by role and, where applicable, by license.
  • License: Some features (e.g. AI blog generation, email campaigns, support tickets) are gated by license tier/feature flags. The API enforces this in middleware and in controllers. See License System for MAIN/CONSUMER modes, tiers, activation, and enforcement.
  • Rate limiting, validation, HTTPS: Applied at the API layer.

Details: Security.


How the Pieces Fit Together (Summary)

LayerComponentsPurpose
ClientsSocial frontend, Admin panelUI; call API via REST (and WebSocket/SignalR for real-time).
APIASP.NET Core, Controllers, MediatR, QuartzHandle requests, write primary data, publish events, run cron jobs.
DataPostgreSQL, MongoDB, Redis, Neo4j, ElasticsearchPersistence and search; written by API and workers.
EventsKafka (and RabbitMQ for blog generation)Decouple API from heavy/async processing.
WorkersKafka consumers + ChatWorker, BlogAutoGenerationWorkerAsync processing, search sync, graph, notifications, chat, AI blogs.
Scheduled jobsQuartz in API + BlogGenerationJob in workerCleanup, sync, email campaigns, backup, license, AI blog schedule.

Reading order from here:

  1. System Overview — Component diagram and request/event flow.
  2. System Architecture Deep DiveDetailed coverage of media processing, user behavior tracking and recommendations, Web/Expo push, search, chat and video calls, and scaling.
  3. Microservices — API surface and worker list.
  4. Data Flow — Sequences for post creation, real-time, caching.
  5. Backend — API and worker project layout, Clean Architecture.
  6. Database — What lives in each store.
  7. Workers — Per-worker responsibilities and events.
  8. Scheduled Jobs — All cron jobs and config keys.
  9. Deployment and Scaling — How to run and scale the system.
  10. Security — Auth, RBAC, license, and hardening.

This document gave you the overall architecture; those links take you deeper into each part.