웹 서비스를 작성한다면 데이터베이스와 상호작용할 가능성이 높습니다. 때로는 원자적으로 적용해야 하는 변경 사항을 적용해야 하는 경우가 있습니다. 모두 성공하거나 모두 성공하지 못합니다. 여기가 트랜잭션이 필요한 곳입니다. 이 기사에서는 누출 추상화 문제를 방지하기 위해 코드에서 트랜잭션을 구현하는 방법을 보여 드리겠습니다.
일반적인 예는 결제 처리입니다.
일반적으로 애플리케이션에는 비즈니스 로직을 데이터베이스 관련 코드와 분리하는 두 개의 모듈이 있습니다.
이 모듈은 SQL 쿼리 등 모든 데이터베이스 관련 작업을 처리합니다. 아래에서는 두 가지 함수를 정의합니다.
import { Injectable } from '@nestjs/common'; import postgres from 'postgres'; @Injectable() export class BillingRepository { constructor( private readonly db_connection: postgres.Sql, ) {} async get_balance(customer_id: string): Promise<number | null> { const rows = await this.db_connection` SELECT amount FROM balances WHERE customer_id=${customer_id} `; return (rows[0]?.amount) ?? null; } async set_balance(customer_id: string, amount: number): Promise<void> { await this.db_connection` UPDATE balances SET amount=${amount} WHERE customer_id=${customer_id} `; } }
서비스 모듈에는 잔액 가져오기, 유효성 검사, 업데이트된 잔액 저장과 같은 비즈니스 로직이 포함되어 있습니다.
import { Injectable } from '@nestjs/common'; import { BillingRepository } from 'src/billing/billing.repository'; @Injectable() export class BillingService { constructor( private readonly billing_repository: BillingRepository, ) {} async bill_customer(customer_id: string, amount: number) { const balance = await this.billing_repository.get_balance(customer_id); // The balance may change between the time of this check and the update. if (balance === null || balance < amount) { return new Error('Insufficient funds'); } await this.billing_repository.set_balance(customer_id, balance - amount); } }
bills_customer 함수에서는 먼저 get_balance를 사용하여 사용자의 잔액을 검색합니다. 그런 다음 잔액이 충분한지 확인하고 set_balance로 업데이트합니다.
위 코드의 문제점은 가져오는 시간과 업데이트되는 시간 사이에 잔액이 변경될 수 있다는 것입니다. 이를 방지하려면 트랜잭션을 사용해야 합니다. 이 문제는 두 가지 방법으로 처리할 수 있습니다.
대신 더 깔끔한 접근 방식을 권장합니다.
트랜잭션을 처리하는 좋은 방법은 트랜잭션 내에서 콜백을 래핑하는 함수를 만드는 것입니다. 이 기능은 불필요한 내부 세부 정보를 노출하지 않는 세션 개체를 제공하여 추상화 누출을 방지합니다. 세션 개체는 트랜잭션 내의 모든 데이터베이스 관련 기능에 전달됩니다.
구현 방법은 다음과 같습니다.
import { Injectable } from '@nestjs/common'; import postgres, { TransactionSql } from 'postgres'; export type SessionObject = TransactionSql<Record<string, unknown>>; @Injectable() export class BillingRepository { constructor( private readonly db_connection: postgres.Sql, ) {} async run_in_session<T>(cb: (sql: SessionObject) => T | Promise<T>) { return await this.db_connection.begin((session) => cb(session)); } async get_balance( customer_id: string, session: postgres.TransactionSql | postgres.Sql = this.db_connection ): Promise<number | null> { const rows = await session` SELECT amount FROM balances WHERE customer_id=${customer_id} `; return (rows[0]?.amount) ?? null; } async set_balance( customer_id: string, amount: number, session: postgres.TransactionSql | postgres.Sql = this.db_connection ): Promise<void> { await session` UPDATE balances SET amount=${amount} WHERE customer_id=${customer_id} `; } }
이 예에서 run_in_session 함수는 트랜잭션을 시작하고 그 안에서 콜백을 실행합니다. SessionObject 유형은 내부 세부 정보 유출을 방지하기 위해 데이터베이스 세션을 추상화합니다. 이제 모든 데이터베이스 관련 기능이 세션 개체를 허용하므로 동일한 트랜잭션에 참여할 수 있습니다.
거래를 활용할 수 있도록 서비스 모듈이 업데이트되었습니다. 그 모습은 다음과 같습니다.
import { Injectable } from '@nestjs/common'; import postgres from 'postgres'; @Injectable() export class BillingRepository { constructor( private readonly db_connection: postgres.Sql, ) {} async get_balance(customer_id: string): Promise<number | null> { const rows = await this.db_connection` SELECT amount FROM balances WHERE customer_id=${customer_id} `; return (rows[0]?.amount) ?? null; } async set_balance(customer_id: string, amount: number): Promise<void> { await this.db_connection` UPDATE balances SET amount=${amount} WHERE customer_id=${customer_id} `; } }
bill_customer_transactional 함수에서는 run_in_session을 호출하고 세션 객체와 함께 콜백을 매개변수로 전달한 다음 이 매개변수를 우리가 호출하는 저장소의 모든 함수에 전달합니다. 이렇게 하면 get_balance와 set_balance가 모두 동일한 트랜잭션 내에서 실행됩니다. 두 호출 사이에 잔액이 변경되면 트랜잭션이 실패하고 데이터 무결성이 유지됩니다.
트랜잭션을 효과적으로 사용하면 특히 여러 단계가 포함될 때 데이터베이스 작업의 일관성이 유지됩니다. 제가 설명한 접근 방식을 사용하면 추상화 유출 없이 트랜잭션을 관리할 수 있어 코드 유지 관리가 더욱 쉬워집니다. 논리를 깔끔하게 유지하고 데이터를 안전하게 유지하려면 다음 프로젝트에서 이 패턴을 구현해 보세요!
읽어주셔서 감사합니다!
?글이 마음에 드셨다면 좋아요 잊지 마세요?
연락처
이 기사가 마음에 들면 주저하지 말고 LinkedIn에 연결하고 Twitter에서 나를 팔로우하세요.
내 메일링 리스트를 구독하세요: https://sergedevs.com
좋아요와 팔로우를 꼭 해주세요 ?
위 내용은 TypeScript에서 트랜잭션 데이터베이스 호출을 작성하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!