0.1.5 • Published 5 months ago

@rstful/drizzle v0.1.5

Weekly downloads
-
License
MIT
Repository
-
Last release
5 months ago

Drizzle Module

이 모듈은 NestJS에서 Drizzle ORM을 사용하기 위한 동적 모듈입니다. 기본 Drizzle 기능과 더불어 CLS(Continuation Local Storage)와의 통합을 통한 고급 기능도 제공합니다.

설치

npm install drizzle-orm mysql2

# CLS 기능을 사용하려면 추가 패키지 설치
npm install nestjs-cls @nestjs-cls/transactional @nestjs-cls/transactional-adapter-drizzle-orm

기본 사용법

1. 동기적 설정 (forRoot)

import { Module } from '@nestjs/common';
import { DrizzleModule } from '@rsnest/drizzle';

@Module({
  imports: [
    DrizzleModule.forRoot({
      host: 'localhost',
      port: 3306,
      user: 'root',
      password: 'password',
      database: 'mydb',
      connectTimeout: 30000,
    }),
  ],
})
export class AppModule {}

2. 비동기적 설정 (forRootAsync)

ConfigService를 사용하여 환경 변수에서 설정을 가져오는 경우:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { DrizzleModule } from '@rsnest/drizzle';

@Module({
  imports: [
    ConfigModule.forRoot(),
    DrizzleModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        user: configService.get('DB_USER'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        connectTimeout: 30000,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

3. 서비스에서 사용

import { Injectable } from '@nestjs/common';
import { DrizzleService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzle: DrizzleService) {}

  async getUsers() {
    return this.drizzle.select().from(users);
  }
}

CLS (Continuation Local Storage) 통합

CLS 기능을 사용하면 다음과 같은 고급 기능을 이용할 수 있습니다:

  • 자동 트랜잭션 관리: @Transactional 데코레이터를 통한 선언적 트랜잭션
  • 요청 컨텍스트 관리: 요청 ID, 사용자 ID, 테넌트 ID 등의 자동 추적
  • 멀티테넌시 지원: 테넌트별 데이터 분리
  • 감사 로그: 자동 사용자 추적 및 메타데이터 생성

1. CLS 모듈 설정

import { Module } from '@nestjs/common';
import { DrizzleClsModule } from '@rsnest/drizzle';

@Module({
  imports: [
    DrizzleClsModule.forRoot({
      host: 'localhost',
      port: 3306,
      user: 'root',
      password: 'password',
      database: 'mydb',
      // CLS 특정 옵션
      mountMiddleware: true,        // 자동 미들웨어 마운트
      enableTransactions: true,     // 트랜잭션 기능 활성화
      global: true,                 // 글로벌 모듈로 설정
    }),
  ],
})
export class AppModule {}

2. 트랜잭션 사용

데코레이터 방식

import { Injectable } from '@nestjs/common';
import { Transactional, DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  @Transactional()
  async createUserAndProfile(userData: any, profileData: any) {
    // 트랜잭션 내에서 실행됨
    const user = await this.drizzleCls.drizzle
      .insert(users)
      .values(userData)
      .returning();

    const profile = await this.drizzleCls.drizzle
      .insert(profiles)
      .values({ ...profileData, userId: user[0].id })
      .returning();

    // 에러가 발생하면 자동으로 롤백됨
    if (Math.random() > 0.5) {
      throw new Error('Random error for testing rollback');
    }

    return { user: user[0], profile: profile[0] };
  }
}

프로그래매틱 방식

import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class UserService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async createUserAndProfile(userData: any, profileData: any) {
    return this.drizzleCls.withTransaction(async () => {
      const user = await this.drizzleCls.drizzle
        .insert(users)
        .values(userData)
        .returning();

      const profile = await this.drizzleCls.drizzle
        .insert(profiles)
        .values({ ...profileData, userId: user[0].id })
        .returning();

      return { user: user[0], profile: profile[0] };
    });
  }
}

3. 컨텍스트 정보 활용

import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class AuditService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async createAuditLog(action: string, entityId: string) {
    const metadata = this.drizzleCls.getAuditMetadata();
    
    return this.drizzleCls.drizzle
      .insert(auditLogs)
      .values({
        action,
        entityId,
        userId: metadata.userId,
        requestId: metadata.requestId,
        timestamp: metadata.timestamp,
      });
  }

  async getUserSpecificData() {
    const userId = this.drizzleCls.getUserId();
    const tenantId = this.drizzleCls.getTenantId();
    
    // 현재 사용자의 데이터만 가져오기
    return this.drizzleCls.drizzle
      .select()
      .from(userDocuments)
      .where(and(
        eq(userDocuments.userId, userId),
        eq(userDocuments.tenantId, tenantId)
      ));
  }
}

4. 미들웨어를 통한 헤더 기반 컨텍스트 설정

CLS 모듈은 자동으로 다음 HTTP 헤더들을 CLS 컨텍스트에 저장합니다:

  • x-request-id: 요청 ID (없으면 자동 생성)
  • x-user-id: 사용자 ID
  • x-tenant-id: 테넌트 ID
# API 호출 예시
curl -H "x-user-id: user-123" \
     -H "x-tenant-id: tenant-456" \
     -H "x-request-id: req-789" \
     http://localhost:3000/api/users

5. 멀티테넌시 지원

import { Injectable } from '@nestjs/common';
import { DrizzleClsService } from '@rsnest/drizzle';

@Injectable()
export class MultiTenantService {
  constructor(private readonly drizzleCls: DrizzleClsService) {}

  async getTenantData() {
    const schemaPrefix = this.drizzleCls.getSchemaPrefix();
    const tenantId = this.drizzleCls.getTenantId();
    
    // 테넌트별 데이터 조회
    return this.drizzleCls.drizzle
      .select()
      .from(tenantData)
      .where(eq(tenantData.tenantId, tenantId));
  }

  async getLogContext() {
    // 로깅을 위한 전체 컨텍스트 정보
    return this.drizzleCls.getLogContext();
    // 결과: { requestId: '...', userId: '...', tenantId: '...', transactionActive: true }
  }
}

API 레퍼런스

DrizzleOptions

속성타입필수기본값설명
hoststring-데이터베이스 호스트
portnumber3306데이터베이스 포트
userstring-데이터베이스 사용자명
passwordstring-데이터베이스 비밀번호
databasestring-데이터베이스 이름
connectTimeoutnumber30000연결 타임아웃 (밀리초)
globalbooleanfalse글로벌 모듈 여부

DrizzleClsOptions

DrizzleOptions의 모든 속성과 추가로:

속성타입필수기본값설명
mountMiddlewarebooleantrueCLS 미들웨어 자동 마운트
enableTransactionsbooleantrue트랜잭션 기능 활성화
namespacestring'drizzle'CLS 네임스페이스

DrizzleClsService 메서드

컨텍스트 관리

  • getRequestId(): 현재 요청 ID 조회
  • getUserId(): 현재 사용자 ID 조회
  • getTenantId(): 현재 테넌트 ID 조회
  • get<T>(key: string): 임의의 CLS 값 조회
  • set<T>(key: string, value: T): 임의의 CLS 값 설정

트랜잭션 관리

  • isTransactionActive(): 트랜잭션 활성 상태 확인
  • withTransaction<T>(callback): 트랜잭션 내에서 콜백 실행
  • withoutTransaction<T>(callback): 트랜잭션 없이 콜백 실행
  • drizzle: 현재 Drizzle 인스턴스 (트랜잭션 지원)

유틸리티

  • getLogContext(): 로깅용 컨텍스트 정보
  • getSchemaPrefix(): 멀티테넌시용 스키마 접두사
  • getAuditMetadata(): 감사 로그용 메타데이터

주의사항

  1. CLS 기능 사용 시: CLS 관련 패키지들이 설치되어 있어야 합니다.
  2. 트랜잭션 사용 시: @Transactional 데코레이터는 메서드에만 사용 가능합니다.
  3. 컨텍스트 접근: CLS 컨텍스트는 요청 생명주기 내에서만 유효합니다.
  4. 멀티테넌시: 테넌트 ID는 HTTP 헤더 또는 수동으로 설정해야 합니다.

License

MIT