Go特有の落とし穴
Go特有の落とし穴
Section titled “Go特有の落とし穴”Go特有の落とし穴と、他言語との違いを詳しく解説します。
1. トランザクション境界は明示的か?
Section titled “1. トランザクション境界は明示的か?”Goのトランザクション管理
Section titled “Goのトランザクション管理”特徴:
// Go: 明示的なトランザクション境界func createOrder(orderData OrderData) (*Order, error) { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }()
// トランザクション内の処理を明示的に記述 order := &Order{UserID: orderData.UserID} if err := tx.Create(order).Error; err != nil { tx.Rollback() return nil, err }
if err := tx.Commit().Error; err != nil { return nil, err }
return order, nil}他言語との比較:
// Java: 宣言的トランザクション管理@Transactionalpublic Order createOrder(OrderData orderData) { Order order = orderRepository.save(new Order(orderData)); return order;}落とし穴:
- トランザクションの見落とし: トランザクションを忘れると、データの整合性が保たれない
- 手動ロールバック: エラー時に手動でロールバックする必要がある
2. 非同期は信頼できるか?
Section titled “2. 非同期は信頼できるか?”Goの非同期処理
Section titled “Goの非同期処理”特徴:
// Go: Goroutineによる非同期処理func processOrder(orderID int64) { go func() { // 非同期処理 paymentService.ChargePayment(orderID) }()}他言語との比較:
// Node.js: Promise/async-awaitasync function processOrder(orderId: number) { await paymentService.chargePayment(orderId);}落とし穴:
- エラーハンドリング: エラーハンドリングを手動で実装する必要がある
- Goroutineリーク: Goroutineが終了しないと、メモリリークが発生する
3. 再実行される前提か?
Section titled “3. 再実行される前提か?”Goの再実行
Section titled “Goの再実行”特徴:
// Go: 再実行は手動で実装する必要があるfunc processOrder(orderID int64) error { maxRetries := 3 for i := 0; i < maxRetries; i++ { err := paymentService.ChargePayment(orderID) if err == nil { return nil } time.Sleep(time.Duration(i+1) * time.Second) } return fmt.Errorf("failed after %d retries", maxRetries)}他言語との比較:
// Java: @Retryableアノテーションで自動リトライ@Retryable(maxAttempts = 3)public PaymentResult chargePayment(Long orderId) { return paymentApiClient.chargePayment(orderId);}落とし穴:
- 再実行の実装漏れ: 再実行ロジックを実装し忘れると、一時的なエラーで処理が失敗する
- 冪等性の確保: 再実行時に冪等性を確保する必要がある
4. after_commit的な逃げ道があるか?
Section titled “4. after_commit的な逃げ道があるか?”Goのトランザクションコミット後処理
Section titled “Goのトランザクションコミット後処理”特徴:
// Go: トランザクションコミット後の処理は手動で実装func createOrder(orderData OrderData) (*Order, error) { order, err := createOrderInTransaction(orderData) if err != nil { return nil, err }
// トランザクションコミット後に外部APIを呼ぶ go func() { paymentService.ChargePayment(order.ID, order.Amount) }()
return order, nil}他言語との比較:
# Ruby (Rails): after_commitコールバックclass Order < ApplicationRecord after_commit :call_external_api
def call_external_api ExternalApi.call(self.id) endend落とし穴:
- トランザクションコミット後の処理: トランザクションコミット後の処理が失敗した場合、ロールバックできない
- 一貫性の保証: トランザクションコミット後の処理が失敗した場合、データの不整合が発生する可能性がある
Go特有の落とし穴のポイント:
- トランザクション境界: 明示的なトランザクション境界、見落としに注意
- 非同期処理: Goroutineによる非同期処理、エラーハンドリングが必要
- 再実行: 手動で実装する必要がある、冪等性の確保が必要
- after_commit的な逃げ道: トランザクションコミット後の処理は手動で実装、一貫性の保証が必要
これらの落とし穴を理解することで、より安全なGoアプリケーションを構築できます。