์ฟผ๋ฆฌ ์ตœ์ ํ™” ํ•˜๊ธฐ 2๋‹จ๊ณ„

ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌํ•˜๊ธฐ

Profile Picture
JangAJang
2023-12-05

์›๋ณธ

๋ฏธ๋ฆฌ๋ณด๋Š” ์ตœ์ข…์š”์•ฝ

ํ…Œ์ŠคํŠธ๊ณผ์ •์—์„œ ๋„๋Œ€์ฒด ์™œ ์ด๋Ÿด๊นŒ๋ฅผ ํ•˜๋ฃจ์ข…์ผ ๊ณ ๋ฏผํ•œ ํ”„๋กœ์ ํŠธ์ด๋‹ค.

ํ™•์‹คํžˆ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๊ตฌํ˜„์ด ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ, ํ…Œ์ŠคํŠธ์‹œ์— ์–ด๋–ค๊ฒŒ ๋ฌธ์ œ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋Š” ๋‹จ์ ๋„ ์žˆ์—ˆ๋‹ค.

๊ธฐ์กด ์ฟผ๋ฆฌ ์ตœ์ ํ™”๋ฅผ ๋ณด๊ณ  ์˜ค๋Š” ๊ฒƒ๋„ ์ถ”์ฒœํ•œ๋‹ค.

์ด๊ฑธ ํ•˜๊ณ  ๋‚˜์„œ ๊ทธ ๋‹ค์Œ์— ์ฒ˜๋ฆฌํ•œ ๊ณผ์ •์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํŠธ๋žœ์žญ์…˜์ด๋ž€? ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜ํ™”์˜ ํ•„์š”์„ฑ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํŠธ๋žœ์žญ์…˜์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ?

์ •๋ง ๊ฐ„๋‹จํ•˜๊ฒŒ, ํŠธ๋žœ์žญ์…˜์€ DB์— ๋ณด๋‚ด๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋ฌถ์Œ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

์ฟผ๋ฆฌ๋ฅผ ํ•˜๋‚˜์”ฉ ๊ณ„์† ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•˜๋‚˜๋ฅผ ํ†ต์งธ๋กœ ๋ณด๋‚ด์„œ ์„ฑ๊ณตํ•˜๋ฉด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ „๋ถ€ ์ ์šฉ์‹œํ‚ค๊ณ (์ปค๋ฐ‹), ์‹คํŒจํ•˜๋ฉด ํ•ด๋‹น ์ฟผ๋ฆฌ๋“ค์— ์˜ํ•œ ๋ณ€๊ฒฝ์„ ์ดˆ๊ธฐํ™”์‹œํ‚ค๋Š”(๋กค๋ฐฑ) ๋ฌถ์Œ์ด๋‹ค.

์ด๋ฅผ ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„œ ์ ์šฉ์‹œ์ผœ์•ผ ํ•  ์ด์œ ๊ฐ€ ์žˆ์—ˆ๋‹ค.

๋ฌธ์ œ์ง‘์„ ๋ณต์‚ฌํ•  ๋•Œ, ๋ณต์‚ฌํ•œ ์›๋ณธ ๋ฌธ์ œ์ง‘์˜ copyCount๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚ค๊ณ ,
๋ณต์‚ฌ๋œ ์งˆ๋ฌธ๋“ค์€ ์ƒˆ๋กœ์šด copy๋œ Question์œผ๋กœ ์ €์žฅ์‹œํ‚จ๋‹ค
์ด๋Ÿฐ ๋กœ์ง์„ ๋งŒ๋“ค์–ด์„œ ์“ฐ๋Š” ๊ณผ์ •์—์„œ, ๋ฌธ์ œ์ง‘ ์›๋ณธ์„ ์ฐพ์•„์„œ copyCount๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚จ ํ›„, Question์„ ๋ณต์‚ฌํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

๊ทผ๋ฐ ๋ณต์‚ฌํ•˜๋ ค๋Š” ์งˆ๋ฌธ์ด ์—†์„ ๋•Œ ์—๋Ÿฌ์ฒ˜๋ฆฌ๊ฐ€ ๋‚˜๊ณ , 400์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด๊ฑธ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“ ๊ฑธ ์กฐํšŒํ•˜๊ณ  ๊ฒ€์ฆํ•œ ํ›„, copyCount๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค๋ฉด ๊ดœ์ฐฎ๊ฒ ์ง€๋งŒ, ์ด๊ฑธ ํŠธ๋žœ์žญ์…˜ํ™” ์‹œ์ผœ์„œ ์‹คํŒจ์‹œ ๊ทธ๋ƒฅ copyCount์˜ ์ฆ๊ฐ€๋ฅผ ๋กค๋ฐฑ์‹œ์ผœ ๋ฒ„๋ฆฌ๋Š” ๊ณผ์ •์„ ์ถ”๊ฐ€ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ํ•™์Šต์ผ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ์ด๋ฅผ ์ ์šฉ์‹œํ‚ค๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

1. QueryRunner์‚ฌ์šฉํ•˜๊ธฐ

nestjs&typeorm์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” QueryRunner๋ฅผ ์ด์šฉํ•ด ์œ„์—์„œ ๋งํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ด๋ณด์•˜๋‹ค.

@Injectable()
export class QuestionService {

  private queryRunner: QueryRunner;

  constructor(
    private questionRepository: QuestionRepository,
    private workbookRepository: WorkbookRepository,
    dataSource: DataSource,
  ) {
    this.queryRunner = dataSource.createQueryRunner();
  }

  async copyQuestions(
    copyQuestionRequest: CopyQuestionRequest,
    member: Member,
  ) {
    await this.queryRunner.connect();
    await this.queryRunner.startTransaction();
    try {
      const workbook = await this.workbookRepository.findById(
        copyQuestionRequest.workbookId,
      );
      validateWorkbook(workbook);
      validateWorkbookOwner(workbook, member);

      const questions = await this.questionRepository.findAllByIds(
        copyQuestionRequest.questionIds,
      );

      Array.from(
        new Set(questions.map((question) => question.workbook)),
      ).forEach(async (workbook) => {
        workbook.increaseCopyCount();
        await this.workbookRepository.update(workbook);
      });

      await this.questionRepository.saveAll(
        questions.map((question) => this.createCopy(question, workbook)),
      );
      await this.queryRunner.commitTransaction();
      return WorkbookIdResponse.of(workbook);
    } catch (e) {
      await this.queryRunner.rollbackTransaction();
    } finally {
      await this.queryRunner.release();
    }
  }
}

์ด๋Ÿฐ์‹์œผ๋กœ QueryRunner๋ฅผ ์—ฐ๊ฒฐ์‹œ์ผœ์ค€ ํ›„, ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค.

return ์ „์— ํŠธ๋žœ์žญ์…˜์„ ์ปค๋ฐ‹ํ•ด์ฃผ๊ณ  returnํ•˜๋ฉฐ, ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋กœ catch๋กœ ๋„˜์–ด๊ฐ€๋ฉด ๋กค๋ฐฑ์„ ์‹œ์ผœ์ค€๋‹ค.

๋ฌด์—‡์ด ๋˜์—ˆ๊ฑด, connect์‹œํ‚จ ์ฟผ๋ฆฌ ๋Ÿฌ๋„ˆ๋ฅผ ๋ฆด๋ฆฌ์ฆˆํ•ด์ค€๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ ํŠธ๋žœ์žญ์…˜ํ™”๋ฅผ ์‹œ์ผœ์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฌธ์„œ๋“ค์„ gitbook, TypeOrm์—์„œ ๋ณด์•˜๋‹ค.

ํ•˜์ง€๋งŒ ์ œ์ผ ํฐ ๋ฌธ์ œ๋Š”, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋„ˆ๋ฌด ๋”๋Ÿฌ์›Œ์งˆ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ ์ด๋‹ค.

๋งค ์ˆœ๊ฐ„๋งˆ๋‹ค ์ €๊ฑธ ๋ฉ”์„œ๋“œ ์•ˆ์— ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•˜๋ฉฐ, try-catch-finally๋ฅผ ๋ฉ”์„œ๋“œํ™” ์‹œํ‚ค๊ณ , ์•ˆ์— ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๋ฅผ ๋„ฃ๊ณ , ๊ทธ ์•ˆ์— return ์ „์— commit์„ ์‹œํ‚จ๋‹ค๋Š” ๊ฒƒ์ด ๋„ˆ๋ฌด ๊ท€์ฐฎ์•˜๋‹ค.

๊ท€์ฐจ๋‹ˆ์ฆ˜์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด์•˜๋‹ค.


2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉํ•˜๊ธฐ(typeorm-transactional)

์ด๊ฑธ ์ฑ„ํƒํ•œ ๊ฐ€์žฅ ํฐ ์ด์œ ๋Š”, JPA์ฒ˜๋Ÿผ @Transactional๋กœ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ํŽธํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋ ‡๊ฒŒ ์–ด๋…ธํ…Œ์ด์…˜(์•„ ์ด ์„ธ๊ณ„์—์„  ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜€๋˜๊ฐ€...)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํŽธํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์—†์„๊นŒ๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

์ด ๋•Œ typeorm-transactional๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.

์ด๊ฑธ ์ด์šฉํ•ด์„œ ๊ทนํ•œ์˜ ์ด๋“์ถฉ์ด ๋˜์–ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

๊ณต์‹๋ฌธ์„œ๋Œ€๋กœ ํ•˜๋‚˜์”ฉ ์ ์šฉํ•ด๋ณด์ž.

// main.ts
async function bootstrap() {
  initializeTransactionalContext();
  const app = await NestFactory.create(AppModule);
  ...

// app.module.ts
@Module({
  imports: [
    TypeOrmModule.forRootAsync(MYSQL_OPTION),
    ....


// MYSQL_OPTION
export const MYSQL_OPTION: TypeOrmModuleAsyncOptions = {
  useFactory() {
    return {
      type: 'mysql',
      name: 'main',
      host: process.env.DATABASE_HOST,
      port: Number(process.env.DATABASE_PORT),
      entities: [Member, Category, Workbook, Question, Answer, Video],
      username: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE,
      autoLoadEntities: true,
      synchronize: true,
    };
  },
  async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }

    return addTransactionalDataSource(new DataSource(options));
  },
};

์ด์ œ TypeOrm์„ ๋น„๋™๊ธฐ ๋ชจ๋“ˆ๋กœ ๋“ฑ๋กํ•ด์ฃผ๊ณ , ์ด ๋•Œ dataSourceFactory๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์—ฌ๊ธฐ์—์„œ addTransactionalDataSource๋ฅผ ํ†ตํ•ด์„œ TransactionalContext๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์ด๋Š” ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋‹ค๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋กœ์ง์ด๋‹ค.

์šฐ๋ฆฌ๋Š” ํ”„๋กœ์ ํŠธ ์‹œ์ž‘์‹œ์— ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์—ฌ๊ธฐ์—์„œ๋งŒ ํŠธ๋žœ์žญ์…˜์„ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด

ํ”„๋กœ์ ํŠธ ์‹คํ–‰์‹œ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ฒŒ ํ–ˆ๋‹ค.

@Transactional()
  async createWorkbook(
    createWorkbookRequest: CreateWorkbookRequest,
    member: Member,
  ) {
    const category = await this.categoryRepository.findByCategoryId(
      createWorkbookRequest.categoryId,
    );
    validateManipulatedToken(member);
    validateCategory(category);

    const workbook = Workbook.of(
      createWorkbookRequest.title,
      createWorkbookRequest.content,
      category,
      member,
      createWorkbookRequest.isPublic,
    );
    const result = await this.workbookRepository.insert(workbook);
    return result.identifiers[0].id as number;
  }

์ด์ œ ์ด๋Ÿฐ ์‹์œผ๋กœ @Transactional()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํ•˜๋‚˜๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

์ด๋Ÿฐ์‹์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ,

์ด๋Ÿฐ์‹์œผ๋กœ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ํ™”๋ฅผ ์‹œํ‚ค๋Š” ์„ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”๋‹ค.

๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…์€ ์ฐธ์ง€์•Š๊ธ”โญ๏ธ

๊ทผ๋ฐ ์ƒ๋‹นํ•œ ๋ฌธ์ œ๋“ค์ด ์žˆ์—ˆ๋‹ค.

ํ•ด๋‹น ๋กœ์ง์œผ๋กœ ํ•„์š”ํ•œ ํŠธ๋žœ์žญ์…˜์„ ๋งŒ๋“ค์—ˆ์„ ๋•Œ,

  1. Transactional is undefined
  2. transaction in transaction
  3. Error: DataSource with name "default" has already added. ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

์ด๊ฑธ๋กœ ๊ฑฐ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ํ„ฐ์ ธ๋ฒ„๋ฆฌ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.

์ด์œ ๊ฐ€ ๋ญ˜๊นŒ?

1. ํŠธ๋žœ์žญ์…˜์ด ๋ญ”์ง€ ๋ชจ๋ฅธ๋‹ค!

jest์—์„œ ํŠธ๋žœ์žญ์…˜์ด ๋ช…์‹œ๋˜์–ด์žˆ๋Š” ๊ฒฝ์šฐ์—, ์ด๋ฅผ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์—์„œ ๋ชจํ‚นํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋‹ค.

jest.mock('typeorm-transactional', () => ({
  Transactional: () => () => ({}),
}));

๋ผ๋Š” ๋กœ์ง์œผ๋กœ ๋ชจํ‚น์€ ๋ชจ๋‘ ํ•ด๊ฒฐํ–ˆ๋‹ค.

2. ํŠธ๋žœ์žญ์…˜ ์†์— ํŠธ๋žœ์žญ์…˜!

์šฐ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€์ ์ธ ํŠธ๋žœ์žญ์…˜์„ ๋งŒ๋“ ์ ์ด ์—†๋Š”๋ฐ, ์™œ ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋‚˜์˜ฌ๊นŒ ๋„ˆ๋ฌด ๊ณ ๋ฏผํ–ˆ๋‹ค.

๊ทผ๋ฐ, ์›์ธ์€ ๋‹จ์ˆœํ•˜๋‹ค.

async save(workbook: Workbook) {
  return await this.repository.save(workbook);
}

save๋ผ๋Š” Type ORM ๊ธฐ๋ณธ ๋กœ์ง์€, ์‚ฌ์‹ค select & update/insert๋ฅผ ํ•œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค. (์‚ฌ์‹ค ์ด๊ฑด ๋‹ค๋“ค ์•Œ๊ฑฐ๋‹ค)

spring์—์„œ @Transactional์„ ์‚ฌ์šฉํ•˜๊ณ , ์•ˆ์—์„œ save๋ฅผ ํ•ด๋„ ์•„๋ฌด ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ์ง€๋งŒ,

nestjs์—์„œ@Transactional์„ ์‚ฌ์šฉํ•˜๊ณ , ์•ˆ์—์„œ save๋ฅผ ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์†์— ํŠธ๋žœ์žญ์…˜์ด ์žˆ์„ ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ๋ฅผ ํ„ฐ๋œจ๋ ธ๋‹ค.

๊ทธ๋ž˜์„œ, ๋‚˜๋Š” save๋กœ์ง์„ ์•„๋ž˜์˜ ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณค๋‹ค.

async save(workbook: Workbook) {
  await this.repository.insert(workbook);
  return this.repository.findOneBy({
    title: workbook.title,
    member: { id: workbook.member.id },
  });
}

์ฝ”๋“œ๋ฅผ ๋ฐ”๊พธ๊ณ  ๋‚˜์„œ, save๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์†์— ์žˆ๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค.

3. ์ด๋ฏธ ๋ฐ์ดํ„ฐ์†Œ์Šค, ํŠธ๋žœ์žญ์…”๋„ ์ปจํ…์ŠคํŠธ๊ฐ€ ์กด์žฌํ•œ๋‹ค!

์‚ฌ์‹ค ์ด๊ฑธ ์ดํ•ดํ•˜๋Š” ๊ณผ์ •์— ์ œ์ผ ์–ด๋ ค์› ๋‹ค.

๋„๋Œ€์ฒด ์™œ ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋‚˜์™”์„๊นŒ?

async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }

    return addTransactionalDataSource(new DataSource(options));
  }

์ด ๋กœ์ง๊ณผ jest๊ฐ€ ๊ฐ™์ด ์žˆ๋Š”๊ฒŒ ๋ฌธ์ œ์˜€๋‹ค.

์•„๊นŒ ํ–ˆ๋˜ ๋ฌธ์žฅ์—์„œ, ํ•˜๋‚˜๋ฅผ ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค.

_์šฐ๋ฆฌ๋Š” ํ”„๋กœ์ ํŠธ ์‹œ์ž‘์‹œ์— ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์—ฌ๊ธฐ์—์„œ๋งŒ ํŠธ๋žœ์žญ์…˜์„ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ํ”„๋กœ์ ํŠธ ์‹คํ–‰์‹œ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ฒŒ ํ–ˆ๋‹ค. _

์ฆ‰, jest๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ…Œ์ŠคํŠธํ™˜๊ฒฝ์—์„œ ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์œ„ํ•œ ์—ฐ๊ฒฐ๋‹ค๋ฆฌ ํ•˜๋‚˜๋งŒ์„ ๋งŒ๋“ ๋‹ค (์ด ๋•Œ ์ด๋ฆ„์„ ๋“ฑ๋กํ•ด์ฃผ์ง€ ์•Š์•„ default๋กœ ๋“ฑ๋ก๋œ๋‹ค)

๊ทผ๋ฐ, jest๋Š” ๋ณ‘๋ ฌ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ฉฐ, ๋‚˜๋Š” ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๋‹ค๋ฅธ Test Module์„ ๋งŒ๋“ ๋‹ค.

์—ฌ๊ธฐ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ๋‹ค๋ฆฌ๋ฅผ ๋‹ค ๋งŒ๋“ค๊ฒŒ๋˜๋Š”๋ฐ?

๊ทธ๋ž˜์„œ ํ„ฐ์กŒ๋‹ค

๋•๋ถ„์— jest๊ฐ€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค๋Š” ๊ฒƒ,

dataSourceFactory์˜ ์ง€๊ธˆ ๋‚ด ๋กœ์ง์—์„œ๋Š” ํ•˜๋‚˜์˜ ์—ฐ๊ฒฐ๋‹ค๋ฆฌ๋กœ๋งŒ ํ†ต์‹ ์„ ํ•œ๋‹ค๋Š” ์ ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค

ํ…Œ์ŠคํŠธ์—์„œ ์ด๊ฑธ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ๋‹ค๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ, ์ด๋ฏธ ์กด์žฌํ•œ๋‹ค๋ฉด ๊ทธ๊ฑธ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚ด๊ฒŒ ์ˆ˜์ •ํ–ˆ๋‹ค.

async dataSourceFactory(options) {
  return (
    getDataSourceByName('default') ||
    addTransactionalDataSource(new DataSource(options))
  );
}

์ด๋Ÿฐ ๋กœ์ง์„ ํ†ตํ•ด default๋ผ๋Š” ์ด๋ฆ„์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•˜๊ณ  ๋๋‚ด๊ณ , ์—†๋‹ค๋ฉด ์ƒˆ๋กญ๊ฒŒ default๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋กœ์ง์œผ๋กœ ๋๋ƒˆ๋‹ค. (๋‘ ํ•จ์ˆ˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ๊ณต ํ•จ์ˆ˜๋‹ค)

์ด๋ ‡๊ฒŒ ํ”„๋กœ๋•์…˜, ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ๋ชจ๋‘ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ์„ฑ๊ณต์‹œ์ผฐ๋‹ค.

ํ…Œ์ŠคํŠธ๊ฐ€ 1ํŽธ๋ณด๋‹ค 12๊ฐœ ์ฆ๊ฐ€ํ•˜๊ณ , ์ „์ฒด ๋กœ์ง ์ˆ˜ํ–‰์‹œ๊ฐ„์ด ์˜คํžˆ๋ ค 0.3์ดˆ ์ค„์–ด๋“œ๋Š” ํšจ๊ณผ๋ฅผ ๋ณด์•˜๋‹ค.

๊ฐœ๊ฟ€์ด๊ตฌ๋งŒ?

์ง„์งœ ๊ฐœ๊ฟ€์ด๊ธฐ๋งŒ ํ• ๊นŒ?

ํ•˜์ง€๋งŒ, ๋งˆ๋ƒฅ ๊ฐœ๊ฟ€์€ ์•„๋‹ˆ์—ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์€ ๋‚ด ์ƒ๊ฐ๋ณด๋‹ค ๋” ์กฐ์‹ฌํ•ด์•ผํ–ˆ๋‹ค.

๋ชจ๋“  ๋กœ์ง์— @Transactional()์„ ๋‹ฌ๊ณ  ์ฒ˜๋ฆฌํ•˜๋ฉด ์ข‹์„๊นŒ๋ฅผ ๋ฉ˜ํ† ๋‹˜๊ป˜ ์—ฌ์ญˆ์–ด๋ณด์•˜์„ ๋•Œ,

๊ทธ๊ฑด ์•„๋‹ˆ๋ผ๋Š” ์ด์•ผ๊ธฐ๊ฐ€ ์žˆ์—ˆ๋‹ค.

๋‚˜๋Š” Spring์—์„œ ๋ชจ๋“  ๋กœ์ง์— @Transactional๋ฅผ ๋‹ฌ์•˜์—ˆ๋Š”๋ฐ,

์ด๋กœ์ธํ•ด ํŠธ๋žœ์žญ์…˜์ด ๊ผฌ์—ฌ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ–ˆ๋‹ค.

์ด์— ๋Œ€ํ•ด ์ฐพ์•„๋ณด์•˜์„ ๋•Œ, ๋ชจ๋“  ๋กœ์ง์˜ ํŠธ๋žœ์žญ์…˜ํ™”๋Š” ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์˜ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ฒŒ ํ•œ๋‹ค๋Š” ์ ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

  • ํŠธ๋žœ์žญ์…˜์˜ ์ค‘์ฒฉ์€ ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ์—ฌ๋Ÿฌ ํŠธ๋žœ์žญ์…˜์ด ๋™์‹œ์— ๋“ค์–ด๊ฐ”์„ ๋•Œ ์ผ๊ด€์„ฑ๊ณผ ์•ˆ์ „์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๊ณ  ํ•œ๋‹ค.
    • ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” typeorm-transactional๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ํŠน์„ฑ์„ ์ด์šฉํ•ด์„œ ์กฐ๊ธˆ์€ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค๊ธฐ๋„ ํ–ˆ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ ์„ค์ •์˜ ์˜ค๋ฒ„ํ—ค๋“œ
    • ์ด์ „์˜ ์ฟผ๋ฆฌ ๋Ÿฌ๋„ˆ๋ฅผ ๋ณด๋ฉด ๋” ์ดํ•ด๊ฐ€ ๋˜๊ธฐ๋„ ํ–ˆ๋‹ค. ์ปค๋„ฅ์…˜์„ ๋งŒ๋“ค๊ณ , ์ปค๋„ฅ์…˜ ์•ˆ์—์„œ ์ปค๋ฐ‹/๋กค๋ฐฑํ›„์— ๋ฆด๋ฆฌ์ฆˆ๋ฅผ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ทธ๋งŒํผ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ์ด๋กœ ์ธํ•œ ๋”œ๋ ˆ์ด/์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

1๋ฒˆ์€ ๊ฒฉ๋ฆฌ์ˆ˜์ค€ ์„ค์ •, ํŠธ๋žœ์žญ์…˜ ์„ค์ •์„ ํ™•์ธํ•˜๋ฉด์„œ ์ง€๋‚˜๊ฐ„๋‹ค๋ฉด ๊ดœ์ฐฎ๊ฒ ์ง€๋งŒ,

2๋ฒˆ์˜ ์ด์œ ์—์„œ ํ™•์‹คํžˆ ๋ชจ๋“  ๋กœ์ง์˜ ํŠธ๋žœ์žญ์…˜ํ™”๊ฐ€ ๋งˆ๋ƒฅ ์ข‹์ง„ ์•Š๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค๊ธฐ๋„ ํ–ˆ๋‹ค.

๋ชจ๋“  ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ๋กค๋ฐฑ์ด ํ™•์‹คํžˆ ํ•„์š”ํ•  ๋•Œ,

๊ทธ๋ฆฌ๊ณ  ๊ฒฉ๋ฆฌ์ˆ˜์ค€์ด ์„ค์ •๋˜์–ด์•ผ ํ•  ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์ด ๋™์ž‘๋˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.

์•ž์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์˜ ํ•„์š”์„ฑ์ด ์žˆ์„ ๋•Œ, ํ™•์‹คํ•˜๊ฒŒ ์„ฑ๋Šฅ์˜ ํ–ฅ์ƒ์ด ์žˆ๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.

๊ทธ๋Ÿผ...twenty thousand...๐Ÿ”ฅ

ยฉ 2024 Adultlee. All rights reserved.Made with โค by ์ด์„ฑ์ธ