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

11 KiB

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

const user = await User.findOne({ email: "john@example.com" });

CouchDB Equivalent

// 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.

MongoDB Query

const streets = await Street.find({
  location: {
    $geoWithin: {
      $box: [[-74.1, 40.6], [-73.9, 40.8]]
    }
  },
  status: "available"
});

CouchDB Equivalent

// 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)

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)

// 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)

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)

// 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

const users = await User.find({ points: { $gt: 0 } })
  .select('name points profilePicture')
  .sort({ points: -1 })
  .limit(10);

CouchDB Equivalent

// 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.

MongoDB Query (Multiple Queries)

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)

// 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

const events = await Event.find({
  date: { $gte: new Date() },
  status: "upcoming"
}).populate('participants', 'name profilePicture');

CouchDB Equivalent

// 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)

const user = await User.findById(userId).populate({
  path: 'earnedBadges',
  populate: {
    path: 'badge',
    model: 'Badge'
  }
});

CouchDB Equivalent (Embedded Data)

// 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

const transactions = await PointTransaction.find({ user: userId })
  .sort({ createdAt: -1 })
  .limit(50);

CouchDB Equivalent

// 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)

const changeStream = User.watch();
changeStream.on('change', (change) => {
  // Handle user changes
});

CouchDB (Changes Feed)

// 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.