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

レッスン 3: デュアルデータベース — PostgreSQL (Drizzle ORM) + MongoDB

構成データ用の Drizzle ORM を使用して PostgreSQL スキーマを設計します。 AI/チャットデータ用のMongoDBドライバー。移行、シード データ、接続プーリング。データベース抽象化レイヤー。

🧠 AI と ML — レッスン 2 レッスン 3: デュアル データベース — PostgreSQL (霧雨 ORM) + MongoDB

AIエージェントプラットフォームをゼロから構築 — xClawとの実戦

パート 1: Monorepo のアーキテクチャとプラットフォーム

xdev.asia

はじめに

xClaw は、データベースを 2 つのシステムに分離します。構造化された構成データ用の 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 による構造化された構成データ — タイプセーフ、移行
  • AI/チャット データ用 MongoDB — 柔軟なスキーマ、TTL インデックス
  • Redis キャッシュ用 — セッション、レート制限
  • 初期セットアップ用の シード データ — 管理者ユーザー、システム ロール

次の記事: Hono を使用した API ゲートウェイの構築 — ルート、ミドルウェア、JWT 認証。