Skip to content

Go特有の落とし穴

Go特有の落とし穴と、他言語との違いを詳しく解説します。

1. トランザクション境界は明示的か?

Section titled “1. トランザクション境界は明示的か?”

特徴:

// 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: 宣言的トランザクション管理
@Transactional
public Order createOrder(OrderData orderData) {
Order order = orderRepository.save(new Order(orderData));
return order;
}

落とし穴:

  • トランザクションの見落とし: トランザクションを忘れると、データの整合性が保たれない
  • 手動ロールバック: エラー時に手動でロールバックする必要がある

特徴:

// Go: Goroutineによる非同期処理
func processOrder(orderID int64) {
go func() {
// 非同期処理
paymentService.ChargePayment(orderID)
}()
}

他言語との比較:

// Node.js: Promise/async-await
async function processOrder(orderId: number) {
await paymentService.chargePayment(orderId);
}

落とし穴:

  • エラーハンドリング: エラーハンドリングを手動で実装する必要がある
  • Goroutineリーク: Goroutineが終了しないと、メモリリークが発生する

特徴:

// 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)
end
end

落とし穴:

  • トランザクションコミット後の処理: トランザクションコミット後の処理が失敗した場合、ロールバックできない
  • 一貫性の保証: トランザクションコミット後の処理が失敗した場合、データの不整合が発生する可能性がある

Go特有の落とし穴のポイント:

  • トランザクション境界: 明示的なトランザクション境界、見落としに注意
  • 非同期処理: Goroutineによる非同期処理、エラーハンドリングが必要
  • 再実行: 手動で実装する必要がある、冪等性の確保が必要
  • after_commit的な逃げ道: トランザクションコミット後の処理は手動で実装、一貫性の保証が必要

これらの落とし穴を理解することで、より安全なGoアプリケーションを構築できます。