外部API呼び出し設計レビュー完全ガイド:タイムアウト・再試行・責務分離の実務判断
外部API呼び出し設計レビュー完全ガイド:タイムアウト・再試行・責務分離の実務判断
Goで外部APIを扱うコードは、レビュー上最も設計品質の差が出やすい領域である。
単に動くコードではなく、失敗を含めた安全設計ができているか、レビューアーは構造を読み解くことが要求される。
本稿は外部API呼び出し処理をレビューする際に実務で注視すべき観点を、良い設計例と詳細なレビュー指摘例を通じて解説するガイドである。
良い実装例:責務分離と制御可能性を両立した設計
type APIClient struct {
    client *http.Client
}
func NewAPIClient() *APIClient {
    return &APIClient{
        client: &http.Client{
            Timeout: 5 * time.Second,
        },
    }
}
func (c *APIClient) Fetch(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, err
    }
    resp, err := c.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}http.Clientの再利用によるコネクション管理の安定化context.Contextによる呼び出し単位のタイムアウト制御- 責務分離によりテスト容易性・拡張性を確保
 
良くない実装例
1. http.Getによる非制御通信
func fetchData(url string) ([]byte, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}
@Reviewerhttp.Get()はhttp.DefaultClientを使用し、Timeout未指定で無制御な長時間待機となります。専用のhttp.Clientを生成し、Timeoutを明示してください。
2. context未使用によるキャンセル不能設計
func callExternal(url string) ([]byte, error) {
    resp, err := http.Get(url)
    ...
}
@Reviewercontext未使用のため、上位設計でのキャンセル・トレース制御が不可能です。必ずcontextを受け取り、NewRequestWithContextで制御可能構造へ修正してください。
3. リトライ判定条件の欠如
func retry(f func() error, max int) error {
    for i := 0; i < max; i++ {
        err := f()
        if err == nil {
            return nil
        }
        time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
    }
    return errors.New("retry failed")
}
@Reviewer失敗原因に関わらず盲目的に再試行しています。HTTPステータスコードやnet.Errorの一時性判定を導入し、再試行対象を限定してください。
4. タイムアウト粒度の混在
client := &http.Client{Timeout: 10 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := client.Do(req)
@Reviewerhttp.Client.Timeoutとcontext.Timeoutが競合しています。呼び出し単位の制御をcontext側に統一し、ClientはTransportレベルのみに使用してください。
5. 責務分離の欠落
func Handler(w http.ResponseWriter, r *http.Request) {
    // 直接API呼び出し実装
    ...
}
@Reviewerハンドラ内で外部API呼び出しロジックを直実装しています。APIクライアント層を分離し、責務分割を行ってください。
外部API呼び出し構造の責務分離モデル
リトライ設計とタイムアウト制御フロー
チェックリスト:外部API呼び出しレビュー観点
| 観点 | チェック内容 | 
|---|---|
| http.Client再利用 | 都度生成されず、共通化・再利用されているか | 
| Timeout設計 | Timeout / context.WithTimeout により呼び出し単位で制御可能か | 
| context受渡 | 全呼び出し経路でcontextが貫通しているか | 
| 再試行設計 | 一時的失敗のみを正しく再試行しているか | 
| 責務分離 | APIクライアントが独立したモジュールに実装されているか | 
| ロギング方針 | 失敗理由が追跡可能な出力が実装されているか | 
| モック容易性 | APIClientがinterface化・DI可能になっているか | 
| 上位停止制御 | 呼び出し側で処理全体のキャンセル制御が可能か | 
まとめ:外部APIレビューは「失敗前提設計力」を読む
API通信は「成功時の動作確認」では設計品質を見抜けない。レビューアーは以下の視点で失敗時も安全に崩れる構造を読み解く必要がある。
- タイムアウト管理が設計レイヤで統制されているか
 - 再試行が安易に乱発されず妥当性判定されているか
 - contextにより呼び出し単位のキャンセルが可能か
 - 責務がハンドラ・ドメイン・通信層で整理されているか
 - テスト容易性(モック可能設計)が成立しているか
 
外部依存は常に落ちる可能性がある前提で設計されるべき責務である。レビューアーは「繋がった場合の設計」ではなく「繋がらなかった場合の耐性」を評価することが任務となる。

