DataSource

장현욱(Artlogy)·2022년 11월 24일
0

TypeORM

목록 보기
3/5
post-thumbnail

DataSource


데이터베이스와 상호작용은 DataSource를 이용한다.
한개의 DataSource는 내가 연결한 하나의 DataBase가 되기 때문에 하나의 애플리케이션에서 여러개의 DataSource를 컨트롤하여 여러 DataBase를 제어 할 수도있다.

DataSource Intialize


새로운 데이터소스 인스턴스를 생성 할려면 다음과 같이 생성자를 초기화 한다.

import { DataSource } from "typeorm"

const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "test",
    password: "test",
    database: "test",
})

AppDataSource.initialize()
    .then(() => {
        console.log("Data Source has been initialized!")
    })
    .catch((err) => {
        console.error("Error during Data Source initialization", err)
    })

글로벌하게 사용할 때가 많으니 싱글톤인스턴스로 전역적으로 사용 할 수 있게 만들어주는게 좋다.
필요한 만큼 다음과 같이 여러 데이터소스를 정의할 수도 있다.

데이터소스를 정의하는데 쓰이는 옵션은 여기에서 확인 할 수 있다.

NestJS

NestJS에서는 다음과 같이 정의한다.

import { AccountEntities } from '@app/account/account.module';
import { FileEntities } from '@app/file/file.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModuleAsyncOptions } from '@nestjs/typeorm';

export const dataBaseConfig: TypeOrmModuleAsyncOptions = {
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    type: 'mariadb',
    host: configService.get<string>('DB_HOST'),
    port: configService.get<number>('DB_PORT'),
    username: configService.get<string>('DB_USER'),
    password: configService.get<string>('DB_PASSWORD'),
    database: configService.get<string>('DB_DATABASE'),
    synchronize: configService.get<boolean>('DB_SYNCHRONIZE'),
    // ADD ENTITIES
    entities: [...AccountEntities, ...FileEntities],
  }),
  inject: [ConfigService],
};

app.module.ts

...
@Module({
  imports: [
    // env
    ConfigModule.forRoot({
      envFilePath:
        process.env.NODE_ENV === 'prod'
          ? ['src/config/env/.prod.env']
          : ['src/config/env/.dev.env'],
    }),
    // database
    TypeOrmModule.forRootAsync(dataBaseConfig),
  ],
})
export class AppModule {}

안정성을 위해 비동기로 처리하는걸 추천한다.

Multiple DataSource


서로 다른 데이터베이스에 연결된 데이터 소스를 이용할려면 다음과 같이 DataSource를 각각 정의해주면 된다.

express

import { DataSource } from "typeorm"

export static const db1DataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "admin",
    database: "db1",
    entities: [__dirname + "/entity/*{.js,.ts}"],
    synchronize: true,
})

export static const db2DataSource = new DataSource({
    type: "postgresql",
    host: "localhost",
    port: 5432,
    username: "root",
    password: "admin",
    database: "db2",
    entities: [__dirname + "/entity/*{.js,.ts}"],
    synchronize: true,
})

nestjs

import { AccountEntities } from '@app/account/account.module';
import { FileEntities } from '@app/file/file.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModuleAsyncOptions } from '@nestjs/typeorm';

export const dataBaseConfig: TypeOrmModuleAsyncOptions = {
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    type: 'mariadb',
    host: configService.get<string>('DB_HOST'),
    port: configService.get<number>('DB_PORT'),
    username: configService.get<string>('DB_USER'),
    password: configService.get<string>('DB_PASSWORD'),
    database: configService.get<string>('DB_DATABASE'),
    synchronize: configService.get<boolean>('DB_SYNCHRONIZE'),
    // ADD ENTITIES
    entities: [...AccountEntities],
  }),
  inject: [ConfigService],
};

export const dataBase2Config: TypeOrmModuleAsyncOptions = {
  imports: [ConfigModule],
  name:'DataSource2',
  useFactory: (configService: ConfigService) => ({
    type: 'postgresql',
    host: configService.get<string>('DB2_HOST'),
    port: configService.get<number>('DB2_PORT'),
    username: configService.get<string>('DB2_USER'),
    password: configService.get<string>('DB2_PASSWORD'),
    database: configService.get<string>('DB2_DATABASE'),
    synchronize: configService.get<boolean>('DB2_SYNCHRONIZE'),
    // ADD ENTITIES
    entities: [ ...FileEntities],
  }),
  inject: [ConfigService],
};

NestJS는 DI구분을 위해 2번째요소 부턴 name을 지정해주어야 한고 다음과 같이 사용한다.

export class SecretService extends TypeOrmQueryService<SecretEntity> {
  constructor(
  		//첫번째 dataSource
       private readonly datasource: DataSource,
        //두번째 dataSource
		@Inject('dataSource2') private readonly datasource2:DataSource,
  ) {
    super(repository);
  }
}

단일환경에서 여러 데이터 베이스 사용


단일 데이터소스에서 여러 데이터베이스를 사용하려면 엔티티별로 이름을 지정해주면 된다.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity({ database: "secondDB" })
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity({ database: "thirdDB" })
export class Photo {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    url: string
}

User테이블은 secondDB에 생성되고 Photo는 thirdDB에 생성된다.
default는 우리가 datasource를 설정 할 때 작성한 DB에 생성된다.

SELECT other DB

다른 데이터베이스에서 데이터를 선택할려면 엔티티모델을 제공하면 된다.

const users = await dataSource
    .createQueryBuilder()
    .select()
    .from(User, "user")
    .addFrom(Photo, "photo")
    .andWhere("photo.userId = user.id")
    .getMany()

이 코드는 다음 SQL쿼리를 생성한다. (DB에 유형에 따라 다름)

SELECT * FROM "secondDB"."user" "user", "thirdDB"."photo" "photo"
    WHERE "photo"."userId" = "user"."id"

엔티티 대신 테이블 경로를 지정할 수도있지만 mysql, mssql에서만 지원된다.

const users = await dataSource
    .createQueryBuilder()
    .select()
    .from("secondDB.user", "user")
    .addFrom("thirdDB.photo", "photo")
    .andWhere("photo.userId = user.id")
    .getMany()

SELECT other Schema


여러 스키마를 사용하려면 shema를 각 앤티티에 설정하기만 하면 된다.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity({ schema: "secondSchema" })
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity({ schema: "thirdSchema" })
export class Photo {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    url: string
}

User엔티티는 secondShema내부에서 생성되고, photo앤티티는 thirdSchema에 생성된다.

Replication

TypeOrm을 사용하면 간단하게 DB Replication을 구현 할 수 있다.


예제

{
  type: "mysql",
  logging: true,
  replication: {
    master: {
      host: "server1",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    },
    slaves: [{
      host: "server2",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    }, {
      host: "server3",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    }]
  }
}

모든 스키마 업데이트 및 쓰기 작업은 master서버를 사용하여 수행된다. find 또는 select로 수행되는(읽기) 쿼리는 레플리카에서 작업하는 걸 추천한다. 인스턴스 지정은 다음과 같이 할 수 있다.

Master Instance

const masterQueryRunner = dataSource.createQueryRunner("master")
try {
    const postsFromMaster = await dataSource
        .createQueryBuilder(Post, "post")
        .setQueryRunner(masterQueryRunner)
        .getMany()
} finally {
    await masterQueryRunner.release()
}

마스터 인스턴스는 모든 일반적인 쓰기/읽기 상황에서 사용하면 된다.

읽기 작업은 레플리카에서 하는건 최적화를 위한 추천일 뿐이지 꼭 그래야되는건 아니다.

Slave Instance

const slaveQueryRunner = dataSource.createQueryRunner("slave")
try {
    const userFromSlave = await slaveQueryRunner.query(
        "SELECT * FROM users WHERE id = $1",
        [userId],
        slaveQueryRunner,
    )
} finally {
    return slaveQueryRunner.release()
}

위 처럼 로우 쿼리를 이용한 조회나 find매서드처럼 읽기(read)작업의 경우 레플리카에서 수행하는 걸 추천한다.

Option

{
  replication: {
    master: {
      host: "server1",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    },
    slaves: [{
      host: "server2",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    }, {
      host: "server3",
      port: 3306,
      username: "test",
      password: "test",
      database: "test"
    }],

    /**
    * true면 연결이 실패 될 때 PoolCluster에서 다시 연결을 시도함
    (Default: true)
    */
    canRetry: true,

    /**
     * 연결이 실패하면 노드의 error카운트가 증가하는데
	 * errorCount가 removeNodeErrorCount보다 클 경우 노드를 제거함
     * 즉 기본값 기준 5번 에러가 발생하면 노드 없앰
     * (Default : 5)
     */
    removeNodeErrorCount: 5,

    /**
     * 연결이 실해 할 경우 다시 연결을 위한 시간을 정함
     * 0일 경우 바로 노드가 제거됌
     */
     restoreNodeTimeout: 0,

    /**
     * 슬레이브 선택 방법을 결정함
     * RR: 교대로 하나를 선택함 (Round-Robin).
     * RANDOM: 랜덤으로 노드를 선택함
     * ORDER: 사용 가능한 가장 앞 노드를 선택함 
     */
    selector: "RR"
  }
}

0개의 댓글