2025. 4. 28. 21:26ใBackend/NestJS
์๋ก
๋ฐฑ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํธ๋์ญ์ ๊ด๋ฆฌ๋ ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์ ์งํ๋ ํต์ฌ ์์์ ๋๋ค. Node.js ํ๊ฒฝ์์ TypeORM์ ์ฌ์ฉํ ๋, ์ฐ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ธ ํธ๋์ญ์ ๊ธฐ๋ฅ์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ณต์กํด์ง์๋ก, ๋จ์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์ ์ด์์ ๊ฒ์ด ํ์ํด์ง๋๋ค.
ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ๊ณผ ํจ๊ป ์น์์ผ ํต์ , ํธ์ ์๋ฆผ, ์ธ๋ถ API ํธ์ถ ๋ฑ ๋ค์ํ ๋ถ๊ฐ ์์ ์ด ํ์ํฉ๋๋ค. ์ด๋ฌํ ์์ ๋ค์ DB ํธ๋์ญ์ ์ฑ๊ณต ์ฌ๋ถ์ ๋ฐ๋ผ ์คํ ์ฌ๋ถ๊ฐ ๊ฒฐ์ ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. TypeORM์ ๊ธฐ๋ณธ ํธ๋์ญ์ API๋ง์ผ๋ก๋ ์ด๋ฌํ ๋ณต์กํ ํ๋ฆ์ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ด๋ ต์ต๋๋ค.
์ด ๊ธ์์๋ TypeORM ํ๊ฒฝ์์ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ ์ ํ์ฉํด ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค. ๋ค๋ฅธ ์ธ์ด์ ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ํธ๋์ญ์ ํ ๊ณผ ๋น๊ตํ๊ณ , Node.js + TypeORM ํ๊ฒฝ์์ ์ด๋ฅผ ์ ์ฉํด๋ณด๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋ณธ๋ก
1. ํธ๋์ญ์ ๊ด๋ฆฌ์ ๋์ ๊ณผ์
๋น์ฆ๋์ค ๋ก์ง๊ณผ ํธ๋์ญ์ ๋ก์ง์ ๊ฒฐํฉ
TypeORM์์ ๊ธฐ๋ณธ์ ์ธ ํธ๋์ญ์ ์ฌ์ฉ์ ์๋์ ๊ฐ์ด ๊ตฌํํฉ๋๋ค:
await dataSource.transaction(async transactionalEntityManager => {
await transactionalEntityManager.save(user);
await transactionalEntityManager.save(userProfile);
});
์ด ํจํด์ ๋จ์ํ ๊ฒฝ์ฐ์๋ ์ ์๋ํ์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ์ง๋ฉดํ๊ฒ ๋ฉ๋๋ค:
๋น์ฆ๋์ค ๋ก์ง๊ณผ ํธ๋์ญ์ ์ฒ๋ฆฌ ๋ก์ง์ด ๊ฒฐํฉ๋จ: ํธ๋์ญ์ ๊ด๋ฆฌ ์ฝ๋๊ฐ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์์ฌ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ์ ํ๋ฉ๋๋ค.
์ฝ๋ ์ค๋ณต: ์ฌ๋ฌ ์๋น์ค์์ ์ ์ฌํ ํธ๋์ญ์ ์ฒ๋ฆฌ ํจํด์ด ๋ฐ๋ณต๋ฉ๋๋ค.
ํก๋จ ๊ด์ฌ์ฌ ์ฒ๋ฆฌ์ ์ด๋ ค์: ํธ๋์ญ์ ์ฑ๊ณต/์คํจ ์ ์คํ๋์ด์ผ ํ๋ ์ถ๊ฐ ๋ก์ง์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ๊ธฐ ์ด๋ ต์ต๋๋ค.
์ค์ NestJS์์ ํธ๋์ญ์ ์ ์ฌ์ฉํ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ์ด๋ฐ ๋ฌธ์ ๊ฐ ๋ช ํํ ๋๋ฌ๋ฉ๋๋ค:
// ๊ธฐ๋ณธ์ ์ธ ํธ๋์ญ์
์ฒ๋ฆฌ ๋ฐฉ์
async createOrder({ itemsToOrder }: CreateOrderRequestDto): Promise<Order> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const order = await queryRunner.manager.save(Order.create({...}));
await queryRunner.manager.save(itemsToOrder.map(item => OrderItem.create({...})));
await queryRunner.commitTransaction();
// ํธ๋์ญ์
์ฑ๊ณต ํ ์๋ฆผ ๋ฐ์ก (DB ์ธ ๋ก์ง)
await this.notificationService.sendOrderCreatedNotification(order);
return order;
} catch (e) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException();
} finally {
await queryRunner.release();
}
}
์ด ์ฝ๋์์๋ ํธ๋์ญ์ ์ด ์ฑ๊ณตํ ํ์๋ง ์๋ฆผ์ ๋ณด๋ด๋ ๋ก์ง์ด ์์ต๋๋ค. ์ด๋ฌํ ํจํด์ด ์ฌ๋ฌ ์๋น์ค์ ๊ฑธ์ณ ๋ฐ๋ณต๋๋ฉด ์ฝ๋ ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๊ณ , ํธ๋์ญ์ ์ฑ๊ณต/์คํจ์ ๋ฐ๋ฅธ ๋ถ๊ฐ ๋ก์ง ์ฒ๋ฆฌ๊ฐ ์ผ๊ด๋์ง ์์ ์ํ์ด ์์ต๋๋ค.
DB ์ธ ๋ก์ง ์ฒ๋ฆฌ์ ๋์
ํธ๋์ญ์ ์ด ์ฑ๊ณตํ ํ์ ์คํ๋์ด์ผ ํ๋ ๋ก์ง๋ค์ ์๊ฐํด ๋ณด์ธ์:
์ด๋ฉ์ผ ๋๋ ํธ์ ์๋ฆผ ๋ฐ์ก
์น์์ผ์ ํตํ ์ค์๊ฐ ์ ๋ฐ์ดํธ
์บ์ ๋ฌดํจํ
์ธ๋ถ ์๋น์ค API ํธ์ถ
๋ก๊น ๋ฐ ๊ฐ์ฌ
์ด๋ฐ ๋ก์ง๋ค์ ํธ๋์ญ์ ์ฑ๊ณต ํ์๋ง ์คํ๋์ด์ผ ํ๋ฉฐ, ํธ๋์ญ์ ์ด ์คํจํ ๊ฒฝ์ฐ ์คํ๋๋ฉด ์ ๋ฉ๋๋ค. ๊ธฐ๋ณธ TypeORM ํธ๋์ญ์ API๋ง์ผ๋ก๋ ์ด๋ฐ ํจํด์ ์ฐ์ํ๊ฒ ๊ตฌํํ๊ธฐ ์ด๋ ต์ต๋๋ค.
2. ๋ค๋ฅธ ํ๊ฒฝ์ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ
๋ค๋ฅธ ํ๋ ์์ํฌ๋ ORM์์๋ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ๊ณ ์์๊น์?
Java Spring์ ํธ๋์ญ์ ๋๊ธฐํ
Spring ํ๋ ์์ํฌ์์๋ TransactionSynchronizationManager๋ฅผ ํตํด ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค:
@Transactional
public void createUser(User user) {
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
emailService.sendWelcomeEmail(user.getEmail());
}
});
}
์ด ์ฝ๋๋ ํธ๋์ญ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ๋ ํ์๋ง ์ด๋ฉ์ผ์ ๋ณด๋ ๋๋ค. Spring 4.2๋ถํฐ๋ ์ ๋ ธํ ์ด์ ๊ธฐ๋ฐ ์ด๋ฒคํธ ํธ๋ค๋ง๋ ์ง์ํฉ๋๋ค.
Sequelize์ ํธ๋์ญ์ ํ
Sequelize๋ ํธ๋์ญ์ ํ ์ ์ง์ ์ง์ํฉ๋๋ค:
const transaction = await sequelize.transaction({
hooks: {
afterCommit: async (tx) => {
await notificationService.send('Transaction completed');
},
afterRollback: async (tx) => {
console.log('Transaction rolled back');
}
}
});
์ด๋ฌํ ํ ์ ํธ๋์ญ์ ์ ์๋ช ์ฃผ๊ธฐ ์ด๋ฒคํธ์ ๋ฐ์ํ ์ ์๊ฒ ํด์ฃผ์ด ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ๊น๋ํ๊ฒ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ด์ฒ๋ผ ๋ค๋ฅธ ์ธ์ด์ ํ๋ ์์ํฌ์์๋ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๋ค์ํ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
3. TypeORM์์์ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ ๊ตฌํ
TypeORM์ ๊ธฐ๋ณธ์ ์ผ๋ก ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ ์ ์ ๊ณตํ์ง ์์ง๋ง, ์ปค๋ฎค๋ํฐ์์ ๊ฐ๋ฐํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ๊ฑฐ๋ ์ง์ ๊ตฌํํ ์ ์์ต๋๋ค.
typeorm-transactional ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ
typeorm-transactional์ TypeORM์์ ํธ๋์ญ์ ์ ๋ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ @Transactional() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ ๊ณตํ๋ฉฐ, ํธ๋์ญ์ ์ฝ๋ฐฑ์ ์ง์ํฉ๋๋ค.
ํธ๋์ญ์ ํ ์ฌ์ฉ:
import { Transactional, runOnTransactionCommit, runOnTransactionRollback } from 'typeorm-transactional';
export class UserService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService,
private logService: LogService
) {}
@Transactional()
async createUser(userData: CreateUserDto): Promise<User> {
const user = await this.userRepository.save(userData);
// ํธ๋์ญ์
์ปค๋ฐ ํ ์คํ๋ ๋ก์ง ๋ฑ๋ก
runOnTransactionCommit(async () => {
await this.emailService.sendWelcomeEmail(user.email);
console.log('User created successfully');
});
// ํธ๋์ญ์
๋กค๋ฐฑ ์ ์คํ๋ ๋ก์ง ๋ฑ๋ก
runOnTransactionRollback(async () => {
await this.logService.logFailure('User creation failed');
});
return user;
}
}
์ด ์ฝ๋์์๋ @Transactional() ๋ฐ์ฝ๋ ์ดํฐ๋ก ๋ฉ์๋๋ฅผ ๊ฐ์ธ๊ณ , runOnTransactionCommit๊ณผ runOnTransactionRollback ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ์ด๋ฒคํธ์ ๋ฐ์ํ๋ ์ฝ๋๋ฅผ ๋ฑ๋กํฉ๋๋ค.
4. ์ค์ ์์ : ๋ฉํฐ ๋๋ฉ์ธ ์๋น์ค์์์ ํธ๋์ญ์ ํ ํ์ฉ (w/ typeorm-transactional)
๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ์ง ์ฃผ๋ฌธ ์์ฑ API๋ฅผ ์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค:
@Injectable()
export class OrderService {
constructor(
private orderRepository: OrderRepository,
private paymentService: PaymentService,
private inventoryService: InventoryService,
private notificationService: NotificationService,
private socketService: SocketService
) {}
@Transactional()
async createOrder(orderData: CreateOrderDto): Promise<Order> {
// 1. ์ฃผ๋ฌธ ์์ฑ
const order = await this.orderRepository.save(new Order(orderData));
// 2. ๊ฒฐ์ ์ฒ๋ฆฌ
await this.paymentService.processPayment(order.id, orderData.paymentInfo);
// 3. ์ฌ๊ณ ๊ฐ์
await this.inventoryService.decreaseStock(orderData.items);
// ํธ๋์ญ์
์ปค๋ฐ ํ ์คํ๋ ๋ก์ง ๋ฑ๋ก
runOnTransactionCommit(async () => {
// ํธ์ ์๋ฆผ ๋ฐ์ก
await this.notificationService.sendOrderConfirmation(order);
// ์น์์ผ์ผ๋ก ์ค์๊ฐ ์
๋ฐ์ดํธ
this.socketService.emitOrderCreated(order.userId, order);
// ์ธ๋ถ ๋ฌผ๋ฅ ์์คํ
API ํธ์ถ
await this.logisticsService.requestShipment(order);
});
// ํธ๋์ญ์
๋กค๋ฐฑ ์ ์คํ๋ ๋ก์ง ๋ฑ๋ก
runOnTransactionRollback(async () => {
// ์คํจ ๋ก๊น
await this.logService.logOrderFailure(orderData, 'Transaction failed');
// ์ฌ์ฉ์์๊ฒ ์คํจ ์๋ฆผ
await this.notificationService.sendOrderFailureNotice(orderData.userId);
});
return order;
}
}
์ด ์์ ์์๋:
์ฃผ๋ฌธ ์์ฑ, ๊ฒฐ์ ์ฒ๋ฆฌ, ์ฌ๊ณ ๊ฐ์๊ฐ ํ๋์ ํธ๋์ญ์ ์์์ ์คํ๋ฉ๋๋ค.
ํธ๋์ญ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ๋ ํ์๋ง ์๋ฆผ ๋ฐ์ก, ์น์์ผ ๋ฉ์์ง ์ ์ก, ์ธ๋ถ API ํธ์ถ์ด ์คํ๋ฉ๋๋ค.
ํธ๋์ญ์ ์ด ์คํจํ๋ฉด ๋กค๋ฐฑ ํ ์คํจ ๋ก๊น ๊ณผ ์คํจ ์๋ฆผ์ด ๋ฐ์ก๋ฉ๋๋ค.
์ด ๋ฐฉ์์ ์ฌ๋ฌ ๋๋ฉ์ธ ์๋น์ค์ ๊ฑธ์น ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ฉด์๋, DB ํธ๋์ญ์ ๊ณผ DB ์ธ ๋ก์ง์ ๋ช ํํ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
๊ฒฐ๋ก
TypeORM ํ๊ฒฝ์์ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ ์ ํ์ฉํ ํก๋จ ๊ด์ฌ์ฌ ์ฒ๋ฆฌ๋ ์ฝ๋์ ๊ฐ๋ ์ฑ, ์ ์ง๋ณด์์ฑ, ์ฌ์ฌ์ฉ์ฑ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค. ํนํ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ด ์์ต๋๋ค:
๊ด์ฌ์ฌ ๋ถ๋ฆฌ: DB ํธ๋์ญ์ ๋ก์ง๊ณผ DB ์ธ ๋ก์ง(์น์์ผ, ์๋ฆผ ๋ฑ)์ ๋ช ํํ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
์ฝ๋ ์ฌ์ฌ์ฉ์ฑ: ํธ๋์ญ์ ์ฑ๊ณต/์คํจ์ ๋ฐ๋ฅธ ๋ก์ง์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
๊ฐ๋ ์ฑ ํฅ์: ๋ณต์กํ ํธ๋์ญ์ ๊ด๋ฆฌ ์ฝ๋๊ฐ @Transactional() ๋ฐ์ฝ๋ ์ดํฐ๋ก ๊ฐ์ํ๋ฉ๋๋ค.
์ ์ง๋ณด์์ฑ ๊ฐ์ : ํธ๋์ญ์ ๊ด๋ จ ๋ก์ง์ด ์ค์ํ๋์ด ๋ณ๊ฒฝ์ด ์ฉ์ดํฉ๋๋ค.
Spring, Sequelize ๋ฑ ๋ค๋ฅธ ํ๋ ์์ํฌ์์ ์ด๋ฏธ ์ ๊ณตํ๋ ํธ๋์ญ์ ์๋ช ์ฃผ๊ธฐ ํ ๊ธฐ๋ฅ์ TypeORM ํ๊ฒฝ์์๋ typeorm-transactional ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ๊ตฌํํ ์ ์์ต๋๋ค. ์ด๋ ํนํ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๋ ๋ณต์กํ ๋๋ฉ์ธ ๋ก์ง์ ๊ฐ์ง ์ ํ๋ฆฌ์ผ์ด์ ์์ ํฐ ๊ฐ์น๋ฅผ ๋ฐํํฉ๋๋ค. ๋ฐ๋ผ์ ๋ง์ฝ ์ด๋ฏธ ํธ๋์ญ์ ํ ์ ์ง์ํ๋ค๋ฉด ์์ ๊ฐ์ด ์ ์ฉํด๋ณด๋ ๊ฒ์, ์ง์ํ์ง ์๋ ํ๊ฒฝ์ด๋ผ๋ฉด ์ง์ ๊ตฌํํด์ ์ฌ์ฉํ๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค.
'Backend > NestJS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
NestJS Request Lifecycle (5) - Exception Filter (0) | 2023.01.13 |
---|---|
NestJS Request Lifecycle (4) - Pipe (0) | 2022.12.29 |
NestJS Request Lifecycle (3) - Interceptor (0) | 2022.12.26 |
NestJS Request Lifecycle (2) - Guard (0) | 2022.12.23 |
NestJS Request Lifecycle (1) - Middleware (0) | 2022.12.22 |