Chuyển đến nội dung chính

第三課:雙資料庫-PostgreSQL(Drizzle ORM)+MongoDB

使用 Drizzle ORM 為設定資料設計 PostgreSQL 架構。用於 AI/聊天資料的 MongoDB 驅動程式。遷移、種子資料、連線池。資料庫抽象層。

🧠 人工智慧與機器學習 — 第 2 課 第三課:雙資料庫-PostgreSQL(毛毛雨 ORM) + MongoDB

從零開始搭建AI代理平台-與xClaw實戰

第 1 部分:Monorepo 架構與平台

亞洲開發網

簡介

xClaw 將資料庫分為兩個系統:用於結構化配置資料的 PostgreSQL 和用於靈活 AI/聊天資料的 MongoDB。本文指導資料庫層的架構設計和實作。


1. 帶有 Drizzle ORM 的 PostgreSQL 模式

1.1 為什麼會下毛毛雨?

特點細雨棱鏡類型ORM
類型安全性編譯時運行時代碼產生裝飾
捆綁尺寸〜50KB〜2MB〜1MB
原始 SQL本地有限公司查詢產生器
移民SQL 優先汽車汽車
效能零開銷代理開銷反射開銷

1.2 模式定義

// packages/db/src/schema/tenants.ts
import { pgTable, uuid, varchar, timestamp, jsonb } from 'drizzle-orm/pg-core';

export const tenants = pgTable('tenants', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: varchar('name', { length: 255 }).notNull(),
  slug: varchar('slug', { length: 255 }).notNull().unique(),
  settings: jsonb('settings').default({}),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  passwordHash: varchar('password_hash', { length: 255 }).notNull(),
  name: varchar('name', { length: 255 }).notNull(),
  tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

export const roles = pgTable('roles', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: varchar('name', { length: 100 }).notNull(),
  tenantId: uuid('tenant_id').references(() => tenants.id).notNull(),
  permissions: jsonb('permissions').default([]),
});

export const userRoles = pgTable('user_roles', {
  userId: uuid('user_id').references(() => users.id).notNull(),
  roleId: uuid('role_id').references(() => roles.id).notNull(),
});

1.3 遷移

# Generate migration từ schema changes
npm run db:generate

# Run migrations
npm run db:migrate

# Visual database browser
npm run db:studio

2. 用於 AI 資料的 MongoDB

2.1 連線設定

// packages/db/src/mongo.ts
import { MongoClient, Db } from 'mongodb';

let client: MongoClient;
let db: Db;

export async function connectMongo(url: string): Promise<Db> {
  client = new MongoClient(url);
  await client.connect();
  db = client.db();

  // Create TTL indexes
  await db.collection('audit_logs').createIndex(
    { createdAt: 1 },
    { expireAfterSeconds: 90 * 24 * 60 * 60 } // 90 days
  );
  await db.collection('system_logs').createIndex(
    { timestamp: 1 },
    { expireAfterSeconds: 30 * 24 * 60 * 60 } // 30 days
  );

  return db;
}

export function getMongo(): Db {
  if (!db) throw new Error('MongoDB not connected');
  return db;
}

2.2 集合結構

// Sessions collection
interface Session {
  _id: string;
  tenantId: string;
  userId: string;
  title: string;
  model: string;
  domainId?: string;
  createdAt: Date;
  updatedAt: Date;
}

// Messages collection
interface Message {
  _id: string;
  sessionId: string;
  role: 'user' | 'assistant' | 'system' | 'tool';
  content: string;
  toolCalls?: ToolCall[];
  toolCallId?: string;
  images?: string[];
  usage?: { promptTokens: number; completionTokens: number };
  timestamp: Date;
}

// Memory entries
interface MemoryEntry {
  _id: string;
  sessionId: string;
  type: 'conversation' | 'entity' | 'summary';
  content: string;
  embedding?: number[];
  createdAt: Date;
}

3. 種子數據

// packages/db/src/seed.ts
export async function seedDatabase(pgDb: DrizzleDB, mongoDB: Db) {
  // Create default tenant
  const [tenant] = await pgDb.insert(tenants).values({
    name: 'Default',
    slug: 'default',
  }).returning();

  // Create admin user (password: password123)
  const passwordHash = await hash('password123', 12);
  const [admin] = await pgDb.insert(users).values({
    email: '[email protected]',
    passwordHash,
    name: 'Admin',
    tenantId: tenant.id,
  }).returning();

  // Create system roles with permissions
  const systemRoles = [
    { name: 'owner', permissions: ALL_PERMISSIONS },        // 60 perms
    { name: 'admin', permissions: ADMIN_PERMISSIONS },      // 52 perms
    { name: 'member', permissions: MEMBER_PERMISSIONS },    // 14 perms
    { name: 'viewer', permissions: VIEWER_PERMISSIONS },    //  8 perms
  ];

  for (const role of systemRoles) {
    await pgDb.insert(roles).values({
      ...role,
      tenantId: tenant.id,
    });
  }
}

4. 總結

  • PostgreSQL + Drizzle ORM 用於結構化設定資料 — 類型安全性、遷移
  • MongoDB 用於 AI/聊天資料 — 靈活的模式、TTL 索引
  • Redis 用於快取 — 會話、速率限制
  • 用於初始設定的種子資料 — 管理員使用者、系統角色

下一篇文章: 使用 Hono 建立 API 閘道 — 路由、中介軟體、JWT 驗證。