CQRS Projections
Architecture Overview
FundlyHub uses CQRS to separate Write operations (Commands) from Read operations (Queries).
- Commands (Writes) go to the Primary Database (Users, Campaigns, Donations tables).
- Events are published to the Events Database.
- Projections (Reads) are built asynchronously in the Analytics/Read Database from these events.
Why Projections?
Projections allow us to serve complex data requirements without complex, slow JOINs on normalized tables. The Read Database contains tables specifically designed for UI views (e.g., "Campaign Card", "Dashboard Stats").
Key Projection Tables
campaign_summary_view
A flat, denormalized view optimized for the "Browse Campaigns" page.
Contains:
- Campaign basic info (title, slug, image)
- Owner details (name, avatar) - Pre-joined
- Category info (name, emoji) - Pre-joined
- Live stats (total raised, donor count) - Pre-calculated
user_dashboard_stats
Aggregated stats for the user dashboard, updated in near real-time.
Contains:
- Total campaigns created
- Total funds raised across all campaigns
- Total unique donors
- Recent activity summary
Querying Projections via API
Frontend clients view projection data by calling standard GET endpoints. The API handles routing the query to the Read Database.
// Frontend: Get Campaign Summary (uses projection)
const getCampaigns = async () => {
// Calls GET /api/v1/fundraisers (Read Model)
const response = await fetch('https://api.fundlyhub.org/api/v1/fundraisers?status=active');
const campaigns = await response.json();
// Data is already "hydrated" with category/owner details
// No need for client-side joins or cascading fetches
campaigns.forEach(c => {
console.log(`${c.title} by ${c.owner_name} (${c.category_name})`);
});
};Eventual Consistency
Because projections are updated by asynchronous event processors, there may be a slight delay (typically milliseconds to seconds) between a write action (e.g., making a donation) and the read model updating (e.g., "Total Raised" increasing).
The frontend handles this optimistically where appropriate, or simply relies on the fast update cycle.