- 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>
468 lines
11 KiB
Markdown
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. |