Drizzle <> Nile

This guide assumes familiarity with:

**공식 웹사이트**에 따르면, Nile은 멀티 테넌트 앱을 위해 재설계된 PostgreSQL입니다.

공식 Nile + Drizzle 빠른 시작마이그레이션 문서를 확인하세요.

Nile은 Drizzle의 모든 Postgres 드라이버와 함께 사용할 수 있으며, 아래에서는 node-postgres의 사용법을 보여드립니다.

단계 1 - 패키지 설치

npm
yarn
pnpm
bun
npm i drizzle-orm postgres
npm i -D drizzle-kit

단계 2 - 드라이버 초기화 및 쿼리 실행

index.ts
// Make sure to install the 'pg' package
import { drizzle } from 'drizzle-orm/node-postgres'

const db = drizzle(process.env.NILEDB_URL);

const response = await db.select().from(...);

기존 드라이버를 제공해야 하는 경우:

index.ts
// Make sure to install the 'pg' package
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});
const db = drizzle({ client: pool });

const response = await db.select().from(...);

가상 테넌트 데이터베이스에 연결

Nile은 가상 테넌트 데이터베이스를 제공합니다. 테넌트 컨텍스트를 설정하면 Nile은 해당 테넌트의 가상 데이터베이스로 쿼리를 전달하며, 모든 쿼리는 해당 테넌트에만 적용됩니다(즉, select * from table은 해당 테넌트의 레코드만 반환합니다).

테넌트 컨텍스트를 설정하기 위해, 트랜잭션을 실행하기 전에 적절한 테넌트 컨텍스트를 설정하는 트랜잭션으로 각 쿼리를 래핑합니다.

테넌트 ID는 단순히 래퍼에 인자로 전달할 수 있습니다:

index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { todosTable, tenants } from "./db/schema";
import { sql } from 'drizzle-orm';
import 'dotenv/config';

const db = drizzle(process.env.NILEDB_URL);

function tenantDB<T>(tenantId: string, cb: (tx: any) => T | Promise<T>): Promise<T> {
  return db.transaction(async (tx) => {
    if (tenantId) {
      await tx.execute(sql`set local nile.tenant_id = '${sql.raw(tenantId)}'`);
    }

    return cb(tx);
  }) as Promise<T>;
}

// In a webapp, you'll likely get it from the request path parameters or headers
const tenantId = '01943e56-16df-754f-a7b6-6234c368b400'

const response = await tenantDB(tenantId, async (tx) => {
    // No need for a "where" clause here
    return await tx.select().from(todosTable);
});

console.log(response);

이를 지원하는 웹 프레임워크를 사용하는 경우, AsyncLocalStorage를 설정하고 미들웨어를 사용하여 테넌트 ID로 채울 수 있습니다. 이 경우 Drizzle 클라이언트 설정은 다음과 같습니다:

import { drizzle } from 'drizzle-orm/node-postgres';
import dotenv from "dotenv/config";
import { sql } from "drizzle-orm";
import { AsyncLocalStorage } from "async_hooks";

export const db = drizzle(process.env.NILEDB_URL);
export const tenantContext = new AsyncLocalStorage<string | undefined>();

export function tenantDB<T>(cb: (tx: any) => T | Promise<T>): Promise<T> {
  return db.transaction(async (tx) => {
    const tenantId = tenantContext.getStore();
    console.log("executing query with tenant: " + tenantId);
    // if there's a tenant ID, set it in the transaction context
    if (tenantId) {
      await tx.execute(sql`set local nile.tenant_id = '${sql.raw(tenantId)}'`);
    }

    return cb(tx);
  }) as Promise<T>;
}

그런 다음, AsyncLocalStorage를 채우도록 미들웨어를 구성하고 요청을 처리할 때 tenantDB 메서드를 사용합니다:

app.ts
// Middleware to set tenant context
app.use("/api/tenants/:tenantId/*", async (c, next) => {
  const tenantId = c.req.param("tenantId");
  console.log("setting context to tenant: " + tenantId);
  return tenantContext.run(tenantId, () => next());
});

// Route handler
app.get("/api/tenants/:tenantId/todos", async (c) => {
    const todos = await tenantDB(c, async (tx) => {
      return await tx
        .select({
          id: todoSchema.id,
          tenant_id: todoSchema.tenantId,
          title: todoSchema.title,
          estimate: todoSchema.estimate,
        })
        .from(todoSchema);
    });
    return c.json(todos);
});

다음 단계는?