{}FireSchema

10 个可复制的 Firestore 数据库结构

带有完整 JSON Schema 示例的真实数据模型

从头开始设计 Firestore 数据库很困难。在编写第一行代码之前,您需要考虑查询、反规范化、子集合和十几个其他 NoSQL 特定问题。与其从零开始,不如使用这 10 个经过验证的模式作为模板。每个都代表一个常见的应用程序架构 — 电子商务、聊天、多租户 SaaS、基于位置的服务 — 带有完整的 JSON Schema,准确显示如何建模集合。复制适合您用例的模式,根据需求进行调整,您将在几分钟而不是几周内拥有坚实的基础。

1. 用户配置文件

社交应用、SaaS 平台、会员网站

Collection Structure

firestore_structures.pattern1_structure
schemas/pattern1.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "users",
  "description": "User profiles with authentication info",
  "schema": {
    "type": "object",
    "properties": {
      "displayName": {
        "type": "string",
        "description": "User's full name"
      },
      "email": {
        "type": "string",
        "format": "email",
        "description": "Login email address"
      },
      "photoURL": {
        "type": "string",
        "format": "uri",
        "description": "Profile picture URL"
      },
      "role": {
        "type": "string",
        "enum": ["admin", "editor", "viewer"],
        "description": "Access control role"
      },
      "createdAt": {
        "type": "string",
        "format": "date-time",
        "description": "Account creation timestamp"
      }
    },
    "required": ["displayName", "email", "role", "createdAt"]
  }
}

Why this design: 将用户配置文件存储为根级文档,使用 Firebase Auth UID 作为文档 ID。反规范化常访问的字段,如 displayName 和 photoURL,以便它们可以嵌入到其他集合中而无需额外读取。

Common mistake: 除非用户设置或首选项有数百个项,否则不要将其嵌套为子集合。保持用户文档精简且快速读取。

2. 电子商务产品和订单

在线商店、市场、库存系统

Collection Structure

firestore_structures.pattern2_structure
schemas/pattern2.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "orders",
  "description": "Customer purchase orders",
  "schema": {
    "type": "object",
    "properties": {
      "userId": {
        "type": "string",
        "description": "Reference to users collection"
      },
      "items": {
        "type": "array",
        "description": "Ordered items with denormalized product data",
        "items": {
          "type": "object",
          "properties": {
            "productId": { "type": "string" },
            "name": { "type": "string" },
            "price": { "type": "number", "minimum": 0 },
            "quantity": { "type": "integer", "minimum": 1 }
          },
          "required": ["productId", "name", "price", "quantity"]
        }
      },
      "total": {
        "type": "number",
        "minimum": 0,
        "description": "Order total in USD"
      },
      "status": {
        "type": "string",
        "enum": ["pending", "confirmed", "shipped", "delivered"],
        "description": "Current order status"
      },
      "createdAt": {
        "type": "string",
        "format": "date-time",
        "description": "Order timestamp"
      }
    },
    "required": ["userId", "items", "total", "status", "createdAt"]
  }
}

Why this design: 将订单存储为带有 userId 的根级文档以进行过滤。将产品名称和价格反规范化到订单项中,以便即使产品数据稍后更改,历史订单仍然准确。

Common mistake: 不要在产品文档中存储库存计数 — 使用单独的库存集合或 Cloud Functions 来防止热门产品上的竞态条件。

3. 聊天和消息

实时聊天应用、客户支持、协作工具

Collection Structure

firestore_structures.pattern3_structure
schemas/pattern3.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "messages",
  "description": "Chat messages within a room",
  "schema": {
    "type": "object",
    "properties": {
      "authorId": {
        "type": "string",
        "description": "User ID of message author"
      },
      "authorName": {
        "type": "string",
        "description": "Denormalized author display name"
      },
      "text": {
        "type": "string",
        "description": "Message content",
        "maxLength": 5000
      },
      "createdAt": {
        "type": "string",
        "format": "date-time",
        "description": "Message timestamp"
      },
      "edited": {
        "type": "boolean",
        "description": "Whether message was edited"
      }
    },
    "required": ["authorId", "authorName", "text", "createdAt"]
  }
}

Why this design: 对消息使用子集合,以便每个聊天室可以容纳无限消息。反规范化 authorName 以避免为每个消息显示获取用户配置文件。在父 chatRoom 中存储 lastMessage 以进行快速预览渲染。

Common mistake: 不要尝试在 Firestore 文档中实现已读回执或输入指示器 — 对临时在线状态数据使用 Realtime Database 或 Firebase Cloud Messaging。

4. 带评论的博客

内容平台、CMS、社区论坛

Collection Structure

firestore_structures.pattern4_structure
schemas/pattern4.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "posts",
  "description": "Blog posts with metadata",
  "schema": {
    "type": "object",
    "properties": {
      "title": {
        "type": "string",
        "description": "Post title",
        "maxLength": 200
      },
      "content": {
        "type": "string",
        "description": "Post body in markdown"
      },
      "authorId": {
        "type": "string",
        "description": "Reference to users collection"
      },
      "authorName": {
        "type": "string",
        "description": "Denormalized author name"
      },
      "publishedAt": {
        "type": "string",
        "format": "date-time",
        "description": "Publication timestamp"
      },
      "commentCount": {
        "type": "integer",
        "minimum": 0,
        "description": "Cached comment count"
      },
      "tags": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Post tags for filtering"
      }
    },
    "required": ["title", "content", "authorId", "authorName", "publishedAt"]
  }
}

Why this design: 将帖子存储为根文档,带有反规范化的作者信息以进行高效的列表渲染。保持一个通过 Cloud Functions 更新的缓存 commentCount,以避免仅为显示计数而读取整个评论子集合。

Common mistake: 不要将完整的评论列表作为数组存储在帖子文档中 — 一旦您有超过几十条评论,它就会崩溃,并使分页变得不可能。

5. 多租户 SaaS

B2B 应用程序、基于工作区的工具、团队管理

Collection Structure

firestore_structures.pattern5_structure
schemas/pattern5.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "organizations",
  "description": "Tenant organizations for multi-tenant SaaS",
  "schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "description": "Organization name"
      },
      "plan": {
        "type": "string",
        "enum": ["free", "pro", "enterprise"],
        "description": "Subscription plan"
      },
      "ownerId": {
        "type": "string",
        "description": "User ID of organization owner"
      },
      "createdAt": {
        "type": "string",
        "format": "date-time",
        "description": "Organization creation timestamp"
      },
      "memberCount": {
        "type": "integer",
        "minimum": 1,
        "description": "Cached member count"
      }
    },
    "required": ["name", "plan", "ownerId", "createdAt"]
  }
}

Why this design: 使用 organizations 作为顶级集合,所有租户数据嵌套在下面。这使安全规则变得简单:用户只能访问其组织内的数据。将成员角色存储在子集合中以便于枚举。

Common mistake: 不要将组织数据复制到用户文档中 — 它会产生同步问题。相反,按用户成员资格过滤的 organizations 集合进行查询。

6. 活动和审计日志

合规性跟踪、用户活动源、管理仪表板

Collection Structure

firestore_structures.pattern6_structure
schemas/pattern6.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "activityLog",
  "description": "Audit trail of user actions",
  "schema": {
    "type": "object",
    "properties": {
      "userId": {
        "type": "string",
        "description": "User who performed the action"
      },
      "userName": {
        "type": "string",
        "description": "Denormalized user name"
      },
      "action": {
        "type": "string",
        "enum": ["created", "updated", "deleted", "viewed"],
        "description": "Action type"
      },
      "resourceType": {
        "type": "string",
        "description": "Type of resource affected (e.g., 'post', 'user')"
      },
      "resourceId": {
        "type": "string",
        "description": "ID of affected resource"
      },
      "metadata": {
        "type": "object",
        "description": "Additional context data"
      },
      "timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "When the action occurred"
      }
    },
    "required": ["userId", "action", "resourceType", "resourceId", "timestamp"]
  }
}

Why this design: 将活动日志存储为带有 userId、action 和 timestamp 索引字段的根集合。通过 Cloud Functions 在文档写入时生成日志条目,以确保即使客户端代码失败也能完成审计跟踪。

Common mistake: 不要尝试将日志存储为用户或资源下的子集合 — 这使得跨用户或跨资源查询变得不可能。使用带引用字段的扁平集合。

7. 地理定位和地图

基于位置的服务、配送应用、商店查找器

Collection Structure

firestore_structures.pattern7_structure
schemas/pattern7.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "locations",
  "description": "Locations with geospatial data",
  "schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "description": "Location name"
      },
      "address": {
        "type": "string",
        "description": "Street address"
      },
      "geopoint": {
        "type": "object",
        "description": "Firestore GeoPoint",
        "properties": {
          "latitude": { "type": "number", "minimum": -90, "maximum": 90 },
          "longitude": { "type": "number", "minimum": -180, "maximum": 180 }
        },
        "required": ["latitude", "longitude"]
      },
      "geohash": {
        "type": "string",
        "description": "Geohash for proximity queries"
      },
      "category": {
        "type": "string",
        "enum": ["restaurant", "store", "office", "other"],
        "description": "Location type"
      }
    },
    "required": ["name", "geopoint", "geohash"]
  }
}

Why this design: 在 GeoPoint 字段旁边存储 geohash 字符串以启用高效的邻近查询。Firestore 无法进行本地半径搜索,因此 geohash 允许您按前缀过滤以查找附近的位置。使用像 geofire-common 这样的库来生成哈希。

Common mistake: 不要尝试直接按纬度/经度范围查询 — Firestore 无法进行复合不等式查询。始终使用 geohash 前缀进行基于位置的过滤。

8. 库存管理

仓库系统、库存跟踪、供应链应用

Collection Structure

firestore_structures.pattern8_structure
schemas/pattern8.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "inventory",
  "description": "Inventory items with stock levels",
  "schema": {
    "type": "object",
    "properties": {
      "sku": {
        "type": "string",
        "description": "Stock keeping unit code"
      },
      "name": {
        "type": "string",
        "description": "Item name"
      },
      "quantity": {
        "type": "integer",
        "minimum": 0,
        "description": "Current stock quantity"
      },
      "warehouseId": {
        "type": "string",
        "description": "Warehouse location reference"
      },
      "reorderLevel": {
        "type": "integer",
        "minimum": 0,
        "description": "Quantity threshold for reorder alerts"
      },
      "lastRestocked": {
        "type": "string",
        "format": "date-time",
        "description": "Last restock timestamp"
      }
    },
    "required": ["sku", "name", "quantity", "warehouseId"]
  }
}

Why this design: 更新 quantity 时使用事务以防止竞态条件。对于频繁更新的高容量库存,考虑使用分布式计数器或单独的事务日志集合以避免写入争用。

Common mistake: 不要从客户端代码递增/递减 quantity 字段而不使用事务 — 在并发访问下您会得到不正确的计数。使用 Cloud Functions 或服务器端逻辑。

9. 社交动态和关注者

社交网络、新闻源、活动流

Collection Structure

firestore_structures.pattern9_structure
schemas/pattern9.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "posts",
  "description": "Social feed posts",
  "schema": {
    "type": "object",
    "properties": {
      "authorId": {
        "type": "string",
        "description": "User ID of post author"
      },
      "authorName": {
        "type": "string",
        "description": "Denormalized author display name"
      },
      "content": {
        "type": "string",
        "description": "Post content",
        "maxLength": 5000
      },
      "likeCount": {
        "type": "integer",
        "minimum": 0,
        "description": "Cached like count"
      },
      "createdAt": {
        "type": "string",
        "format": "date-time",
        "description": "Post timestamp"
      }
    },
    "required": ["authorId", "authorName", "content", "createdAt"]
  }
}

Why this design: 将 followers 和 following 存储为每个用户下的单独子集合,以在两个方向上启用高效查询。保持一个通过 Cloud Functions 更新的缓存 likeCount,而不是读取整个 likes 子集合。

Common mistake: 不要在写入时将帖子展开到所有关注者的动态中 — 它无法扩展。相反,使用关注 ID 上的 array-contains 查询实时查询来自关注用户的帖子。

10. IoT 和传感器数据

IoT 仪表板、环境监测、设备遥测

Collection Structure

firestore_structures.pattern10_structure
schemas/pattern10.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "collection": "readings",
  "description": "Sensor readings from IoT devices",
  "schema": {
    "type": "object",
    "properties": {
      "temperature": {
        "type": "number",
        "description": "Temperature in Celsius"
      },
      "humidity": {
        "type": "number",
        "minimum": 0,
        "maximum": 100,
        "description": "Relative humidity percentage"
      },
      "batteryLevel": {
        "type": "number",
        "minimum": 0,
        "maximum": 100,
        "description": "Battery percentage"
      },
      "timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "Reading timestamp"
      }
    },
    "required": ["timestamp"]
  }
}

Why this design: 将时间序列数据存储为每个设备下的子集合。对于高频数据,批量写入并通过 Cloud Functions 实现 TTL 清理以避免无限增长。考虑 Cloud Firestore TTL 策略或 BigQuery 导出用于长期分析。

Common mistake: 如果您的设备每秒报告一次,不要存储每个传感器读数 — 您会超过 Firestore 的写入限制和成本。聚合到分钟或小时间隔,或对高频写入使用 Realtime Database。

将这些模式转化为文档

这些 JSON Schema 不仅仅是示例 — 它们是您可以在项目中使用的工作文档。将它们保存为代码仓库中的 .schema.json 文件,使用 FireSchema 渲染它们,并为您的团队提供一个他们实际会使用的可浏览参考。设置只需 5 分钟。

下一步

既然您有了可使用的模式,请学习如何有效使用它们:

试用 FireSchema

快速开始指南