Files
adopt-a-street/COUCHDB_QUERY_EXAMPLES.md
William Valentin e74de09605 feat: design comprehensive CouchDB document structure
- Add detailed CouchDB design document with denormalization strategy
- Create migration script for MongoDB to CouchDB transition
- Implement CouchDB service layer with all CRUD operations
- Add query examples showing performance improvements
- Design supports embedded data for better read performance
- Include Mango indexing strategy for optimal query patterns
- Provide data consistency and migration strategies

This design prioritizes read performance and user experience for the
social community nature of the Adopt-a-Street application.

🤖 Generated with AI Assistant

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
2025-11-01 12:57:49 -07:00

468 lines
11 KiB
Markdown

# CouchDB Query Examples
This document demonstrates how the CouchDB design handles common query patterns for the Adopt-a-Street application.
## 1. User Authentication
### MongoDB Query
```javascript
const user = await User.findOne({ email: "john@example.com" });
```
### CouchDB Equivalent
```javascript
// Using Mango query
const user = await couchdbService.findUserByEmail("john@example.com");
// Raw Mango query
{
"selector": {
"type": "user",
"email": "john@example.com"
},
"limit": 1
}
```
**Performance**: Single document lookup with indexed email field.
## 2. Geospatial Street Search
### MongoDB Query
```javascript
const streets = await Street.find({
location: {
$geoWithin: {
$box: [[-74.1, 40.6], [-73.9, 40.8]]
}
},
status: "available"
});
```
### CouchDB Equivalent
```javascript
// Using service method
const streets = await couchdbService.findStreetsByLocation([[-74.1, 40.6], [-73.9, 40.8]]);
// Raw Mango query
{
"selector": {
"type": "street",
"status": "available",
"location": {
"$geoWithin": {
"$box": [[-74.1, 40.6], [-73.9, 40.8]]
}
}
}
}
```
**Performance**: Geospatial index on location field, filtered by status.
## 3. User's Activity Feed
### MongoDB Query (Multiple Queries)
```javascript
const posts = await Post.find({ user: userId }).sort({ createdAt: -1 });
const tasks = await Task.find({ completedBy: userId }).sort({ updatedAt: -1 });
const events = await Event.find({ participants: userId }).sort({ date: -1 });
// Combine and sort results
const activity = [...posts, ...tasks, ...events].sort((a, b) => b.createdAt - a.createdAt);
```
### CouchDB Equivalent (Single Query)
```javascript
// Using service method
const activity = await couchdbService.getUserActivity(userId, 50);
// Raw Mango query
{
"selector": {
"$or": [
{
"type": "post",
"user.userId": "user_1234567890abcdef"
},
{
"type": "task",
"completedBy.userId": "user_1234567890abcdef"
},
{
"type": "event",
"participants": {
"$elemMatch": {
"userId": "user_1234567890abcdef"
}
}
}
]
},
"sort": [{"createdAt": "desc"}],
"limit": 50
}
```
**Performance**: Single query with compound OR selector, sorted by creation date.
## 4. Social Feed with User Data
### MongoDB Query (Population Required)
```javascript
const posts = await Post.find({})
.populate('user', 'name profilePicture')
.sort({ createdAt: -1 })
.limit(20);
// For comments, separate query needed
const comments = await Comment.find({ post: { $in: posts.map(p => p._id) } })
.populate('user', 'name profilePicture')
.sort({ createdAt: 1 });
```
### CouchDB Equivalent (No Population Needed)
```javascript
// Get posts with embedded user data
const posts = await couchdbService.getSocialFeed(20);
// Get comments for posts
const postIds = posts.map(p => p._id);
const comments = await couchdbService.getPostComments(postIds[0]); // Example for one post
// Raw Mango query for posts
{
"selector": {
"type": "post"
},
"sort": [{"createdAt": "desc"}],
"limit": 20
}
// Raw Mango query for comments
{
"selector": {
"type": "comment",
"post.postId": {"$in": ["post_123", "post_456"]}
},
"sort": [{"createdAt": "asc"}]
}
```
**Performance**: User data embedded in posts, no additional lookups needed.
## 5. Leaderboard
### MongoDB Query
```javascript
const users = await User.find({ points: { $gt: 0 } })
.select('name points profilePicture')
.sort({ points: -1 })
.limit(10);
```
### CouchDB Equivalent
```javascript
// Using service method
const leaderboard = await couchdbService.getLeaderboard(10);
// Raw Mango query
{
"selector": {
"type": "user",
"points": {"$gt": 0}
},
"sort": [{"points": "desc"}],
"limit": 10,
"fields": ["_id", "name", "points", "profilePicture", "stats"]
}
```
**Performance**: Indexed query on points field with descending sort.
## 6. Street Details with Related Data
### MongoDB Query (Multiple Queries)
```javascript
const street = await Street.findById(streetId).populate('adoptedBy', 'name profilePicture');
const tasks = await Task.find({ street: streetId });
const reports = await Report.find({ street: streetId });
// Calculate stats manually
const stats = {
tasksCount: tasks.length,
completedTasksCount: tasks.filter(t => t.status === 'completed').length,
reportsCount: reports.length,
openReportsCount: reports.filter(r => r.status === 'open').length
};
```
### CouchDB Equivalent (Single Document)
```javascript
// Single document contains all needed data
const street = await couchdbService.getById(streetId);
// Raw Mango query
{
"selector": {
"_id": "street_abc123def456"
}
}
// Result includes embedded stats:
{
"_id": "street_abc123def456",
"type": "street",
"name": "Main Street",
"adoptedBy": {
"userId": "user_123",
"name": "John Doe",
"profilePicture": "https://cloudinary.com/image.jpg"
},
"stats": {
"tasksCount": 5,
"completedTasksCount": 3,
"reportsCount": 2,
"openReportsCount": 1
}
}
```
**Performance**: Single document lookup with pre-calculated stats.
## 7. Event Management
### MongoDB Query
```javascript
const events = await Event.find({
date: { $gte: new Date() },
status: "upcoming"
}).populate('participants', 'name profilePicture');
```
### CouchDB Equivalent
```javascript
// Using service method
const events = await couchdbService.findByType('event', {
date: { $gte: new Date().toISOString() },
status: 'upcoming'
});
// Raw Mango query
{
"selector": {
"type": "event",
"date": {"$gte": "2024-01-15T00:00:00Z"},
"status": "upcoming"
},
"sort": [{"date": "asc"}]
}
// Result includes embedded participant data:
{
"_id": "event_123",
"type": "event",
"title": "Community Cleanup",
"participants": [
{
"userId": "user_123",
"name": "John Doe",
"profilePicture": "https://cloudinary.com/image.jpg",
"joinedAt": "2024-01-10T10:00:00Z"
}
],
"participantsCount": 1
}
```
**Performance**: Participant data embedded, no population needed.
## 8. Badge System
### MongoDB Query (Complex Join)
```javascript
const user = await User.findById(userId).populate({
path: 'earnedBadges',
populate: {
path: 'badge',
model: 'Badge'
}
});
```
### CouchDB Equivalent (Embedded Data)
```javascript
// Single user document contains badge data
const user = await couchdbService.findUserById(userId);
// Raw Mango query
{
"selector": {
"_id": "user_1234567890abcdef"
}
}
// Result includes embedded badges:
{
"_id": "user_1234567890abcdef",
"type": "user",
"name": "John Doe",
"earnedBadges": [
{
"badgeId": "badge_123",
"name": "Street Hero",
"description": "Adopted 5 streets",
"icon": "🏆",
"rarity": "rare",
"earnedAt": "2024-01-15T10:30:00Z",
"progress": 100
}
],
"stats": {
"badgesEarned": 1
}
}
```
**Performance**: Badge data embedded in user document, no joins required.
## 9. Point Transaction History
### MongoDB Query
```javascript
const transactions = await PointTransaction.find({ user: userId })
.sort({ createdAt: -1 })
.limit(50);
```
### CouchDB Equivalent
```javascript
// Using service method
const transactions = await couchdbService.find({
type: 'point_transaction',
'user.userId': userId
}, {
sort: [{ createdAt: 'desc' }],
limit: 50
});
// Raw Mango query
{
"selector": {
"type": "point_transaction",
"user.userId": "user_1234567890abcdef"
},
"sort": [{"createdAt": "desc"}],
"limit": 50
}
// Result includes embedded user data:
{
"_id": "transaction_123",
"type": "point_transaction",
"user": {
"userId": "user_123",
"name": "John Doe"
},
"amount": 10,
"type": "task_completion",
"description": "Completed task: Clean up litter",
"relatedEntity": {
"entityType": "Task",
"entityId": "task_456",
"entityName": "Clean up litter"
},
"balanceAfter": 150
}
```
**Performance**: Indexed query on user and creation date.
## 10. Real-time Updates with Changes Feed
### MongoDB (Change Streams)
```javascript
const changeStream = User.watch();
changeStream.on('change', (change) => {
// Handle user changes
});
```
### CouchDB (Changes Feed)
```javascript
// Listen to changes feed
const changes = couchdbService.db.changes({
since: 'now',
live: true,
include_docs: true
});
changes.on('change', (change) => {
const doc = change.doc;
// Handle different document types
switch (doc.type) {
case 'user':
// Handle user updates
break;
case 'post':
// Handle new posts
break;
case 'event':
// Handle event updates
break;
}
});
// Filter by document type
const userChanges = couchdbService.db.changes({
since: 'now',
live: true,
include_docs: true,
filter: '_design/app',
selector: {
type: 'user'
}
});
```
**Performance**: Native real-time updates with filtering capabilities.
## Performance Comparison Summary
| Query Pattern | MongoDB | CouchDB | Performance Impact |
|---------------|---------|---------|-------------------|
| User Auth | 1 query + index | 1 query + index | Similar |
| Social Feed | 1 query + populate | 1 query (embedded) | CouchDB faster |
| User Activity | 3 queries + combine | 1 query (OR) | CouchDB faster |
| Leaderboard | 1 query + index | 1 query + index | Similar |
| Street Details | 4 queries + calc | 1 query (embedded) | CouchDB much faster |
| Event Management | 1 query + populate | 1 query (embedded) | CouchDB faster |
| Badge System | Complex populate | Embedded data | CouchDB much faster |
| Real-time Updates | Change Streams | Changes Feed | CouchDB more flexible |
## Key Benefits of CouchDB Design
1. **Reduced Query Complexity**: Most common queries become single-document lookups
2. **Better Read Performance**: Embedded data eliminates JOIN operations
3. **Simplified Application Logic**: No need for complex population strategies
4. **Improved Offline Support**: Rich documents enable better offline functionality
5. **Real-time Capabilities**: Native changes feed with flexible filtering
6. **Scalability**: Denormalized design scales well with read-heavy workloads
## Trade-offs and Mitigations
1. **Data Duplication**: User data appears in multiple documents
- **Mitigation**: Use changes feed to propagate updates
2. **Update Complexity**: Changes require updating multiple documents
- **Mitigation**: Batch updates and background reconciliation jobs
3. **Storage Overhead**: Larger documents due to embedded data
- **Mitigation**: Selective embedding based on access patterns
4. **Consistency**: Eventual consistency for related data
- **Mitigation**: Application-level consistency checks and reconciliation
This CouchDB design prioritizes read performance and user experience, which aligns perfectly with the social community nature of the Adopt-a-Street application where most operations are reads rather than writes.