Node.js特有の落とし穴
Node.js特有の落とし穴
Section titled “Node.js特有の落とし穴”Node.js特有の落とし穴と、他言語との違いを詳しく解説します。
1. トランザクション境界は明示的か?
Section titled “1. トランザクション境界は明示的か?”Node.jsのトランザクション管理
Section titled “Node.jsのトランザクション管理”特徴:
// Node.js: 明示的なトランザクション境界async function createOrder(orderData: OrderData): Promise<Order> { return await prisma.$transaction(async (tx) => { // トランザクション内の処理を明示的に記述 const order = await tx.order.create({ data: orderData }); return order; });}他言語との比較:
// Java: 宣言的トランザクション管理@Transactionalpublic Order createOrder(OrderData orderData) { // アノテーションでトランザクションを管理 Order order = orderRepository.save(new Order(orderData)); return order;}落とし穴:
- トランザクションの見落とし: トランザクションを忘れると、データの整合性が保たれない
- ネストしたトランザクション: ネストしたトランザクションの動作を理解する必要がある
2. 非同期は信頼できるか?
Section titled “2. 非同期は信頼できるか?”Node.jsの非同期処理
Section titled “Node.jsの非同期処理”特徴:
// Node.js: Promise/async-awaitによる非同期処理async function fetchData(): Promise<Data> { const data = await fetch('https://api.example.com/data'); return await data.json();}他言語との比較:
// Java: CompletableFutureによる非同期処理CompletableFuture<Data> future = CompletableFuture.supplyAsync(() -> { return externalApi.call();});落とし穴:
- 未処理のPromise: Promiseが処理されないと、エラーが隠蔽される
- エラーハンドリング: 適切なエラーハンドリングが必要
3. 再実行される前提か?
Section titled “3. 再実行される前提か?”Node.jsの再実行
Section titled “Node.jsの再実行”特徴:
// Node.js: 再実行は手動で実装する必要があるasync function processOrder(orderId: number): Promise<void> { try { await paymentService.chargePayment(orderId); } catch (error) { // リトライロジックを手動で実装 await retry(() => paymentService.chargePayment(orderId), 3); }}他言語との比較:
// Java: @Retryableアノテーションで自動リトライ@Retryable(maxAttempts = 3)public PaymentResult chargePayment(Long orderId) { return paymentApiClient.chargePayment(orderId);}落とし穴:
- 再実行の実装漏れ: 再実行ロジックを実装し忘れると、一時的なエラーで処理が失敗する
- 冪等性の確保: 再実行時に冪等性を確保する必要がある
4. after_commit的な逃げ道があるか?
Section titled “4. after_commit的な逃げ道があるか?”Node.jsのトランザクションコミット後処理
Section titled “Node.jsのトランザクションコミット後処理”特徴:
// Node.js: トランザクションコミット後の処理は手動で実装async function createOrder(orderData: OrderData): Promise<Order> { const order = await prisma.$transaction(async (tx) => { return await tx.order.create({ data: orderData }); });
// トランザクションコミット後に外部APIを呼ぶ await paymentService.chargePayment(order.id);
return order;}他言語との比較:
# Ruby (Rails): after_commitコールバックclass Order < ApplicationRecord after_commit :call_external_api
def call_external_api ExternalApi.call(self.id) endend落とし穴:
- トランザクションコミット後の処理: トランザクションコミット後の処理が失敗した場合、ロールバックできない
- 一貫性の保証: トランザクションコミット後の処理が失敗した場合、データの不整合が発生する可能性がある
5. イベントループのブロック
Section titled “5. イベントループのブロック”Node.jsのイベントループ
Section titled “Node.jsのイベントループ”特徴:
// ❌ 問題: イベントループをブロックconst data = fs.readFileSync('large-file.txt', 'utf8'); // ブロッキング操作
// ✅ 解決: 非ブロッキング操作const data = await fs.promises.readFile('large-file.txt', 'utf8');他言語との比較:
// Java: マルチスレッドにより、ブロッキング操作でも他のリクエストを処理可能public String readFile(String filePath) throws IOException { return Files.readString(Paths.get(filePath)); // ブロッキング操作でも問題ない}落とし穴:
- イベントループのブロック: 同期I/O操作により、イベントループがブロックされる
- 他のリクエストの処理不可: イベントループがブロックされている間、他のリクエストが処理できない
Node.js特有の落とし穴のポイント:
- トランザクション境界: 明示的なトランザクション境界、見落としに注意
- 非同期処理: Promise/async-awaitによる非同期処理、未処理のPromiseに注意
- 再実行: 手動で実装する必要がある、冪等性の確保が必要
- after_commit的な逃げ道: トランザクションコミット後の処理は手動で実装、一貫性の保証が必要
- イベントループのブロック: 同期I/O操作によりイベントループがブロックされる、非ブロッキング操作を使用
これらの落とし穴を理解することで、より安全なNode.jsアプリケーションを構築できます。