Article

API連携 機能用語集|専門用語をやさしく解説

高田晃太郎
API連携 機能用語集|専門用語をやさしく解説

API連携 機能用語集|専門用語をやさしく解説

書き出し(導入) 各種業界調査(PostmanのState of the API、Stack Overflow Developer Surveyなど)では、APIは新規収益と開発効率の両輪であることが繰り返し示されている。¹²にもかかわらず、現場では用語の解像度の差が原因で、設計判断が遅れたり、不要な再実装が生じることが多い。CTOやエンジニアリーダーが共通言語を持てば、要件定義からSLO設計、実装・運用・コスト評価までの意思決定速度は大幅に向上する。本稿はAPI連携の専門用語を、実装と経営の双方の観点で整理し、完全なコード例とベンチマーク、ROI計算の要点までを一気通貫で示す。

用語の整理と設計判断の軸

前提条件と環境

  • 対象: API連携を主導するCTO/EM/Tech Lead、中級〜上級エンジニア
  • サンプル環境: Node.js 18、Python 3.11、Go 1.22、Java 17、MacBook Pro M2/32GB、ローカル/社内ネットワーク
  • 目標: 正確な用語理解→設計判断→実装→計測→ROI評価までの意思決定時間短縮

技術仕様(代表用語)

用語定義プロトコル/規格主な用途設計の勘所
RESTリソース指向のHTTP APIHTTP/1.1/2、JSON汎用CRUD、B2B連携冪等性、HTTPステータス、キャッシュ制御
GraphQLクライアント主導のクエリ言語³HTTP/WS集約クエリ、複数ソース統合N+1対策、スキーマガバナンス⁴
gRPCバイナリRPCHTTP/2、Protobuf低レイテンシ、内部マイクロサービス双方向ストリーミング、IDL契約
OpenAPIAPI契約仕様⁵YAML/JSON契約駆動開発、コード生成バージョニング、互換性管理
OAuth2/OIDC認可/認証⁶RFC6749/OIDC第三者委譲、SSOトークンスコープ、トークン保護
API Gateway集中制御プレーンN/Aルーティング、認証、制限レイテンシ/スループット、冗長化
Rate Limit単位時間あたりの制限Token Bucket/Leaky濫用防止、保護429設計、Retry-After提示⁹
Retry/Backoff再試行/指数遅延⁷ポリシー一過性障害回復冪等操作に限定、上限/ジッタ⁷
Idempotency繰り返しでも同じ結果ヘッダ/キー¹⁰決済/注文APIキー保管、再送窓口¹⁰
Paginationデータ分割取得Offset/Cursor一覧取得カーソル推奨、整合順序
Webhook事後通知コールバックHTTPイベント駆動署名検証、再送・順序保証¹¹
Circuit Breaker断路による保護ポリシーカスケード障害防止開閉基準、フォールバック⁸
mTLS双方向TLSTLSゼロトラスト証明書ローテ、秘密管理
Observability可観測性¹²OTel/OTLP分散トレーシングトレース連携、SLI設計¹²

設計判断の軸は、データ量・対話性・レイテンシ要件・信頼境界・スキーマの進化速度を基準に選ぶ。例えば内部間はgRPC、外部公開はREST+OpenAPI、集約はGraphQLという住み分けが一般的だ。³⁵認証は「外部=OAuth2、内部=mTLS+短命トークン」⁶、スロットリングはゲートウェイで集中管理、再試行はクライアント内で冪等操作に限定する⁷。

実装パターンとコード例(完全版)

  1. Axios + 冪等キー + 再試行(Node.js)¹⁰⁷
import axios from 'axios';
import axiosRetry from 'axios-retry';
import { v4 as uuidv4 } from 'uuid';

const client = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 3000,
  headers: { 'User-Agent': 'TechLeadInsights/1.0' },
});

axiosRetry(client, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status >= 500,
});

async function createOrder(payload) {
  const idempotencyKey = uuidv4();
  try {
    const res = await client.post('/orders', payload, {
      headers: { 'Idempotency-Key': idempotencyKey },
      validateStatus: (s) => s >= 200 && s < 500,
    });
    if (res.status >= 400) throw new Error(`Upstream error ${res.status}: ${res.data?.message ?? 'unknown'}`);
    return res.data;
  } catch (err) {
    console.error('createOrder failed', { idempotencyKey, message: err.message });
    throw err;
  }
}

(async () => {
  try {
    const order = await createOrder({ sku: 'ABC', qty: 2 });
    console.log(order);
  } catch (e) {
    process.exitCode = 1;
  }
})();
  1. httpx + Circuit Breaker + Exponential Backoff(Python)⁸⁷
import asyncio
import httpx
import backoff
from pybreaker import CircuitBreaker, CircuitBreakerError

breaker = CircuitBreaker(fail_max=5, reset_timeout=30)

@backoff.on_exception(backoff.expo, (httpx.ConnectError, httpx.ReadTimeout), max_time=20)
@breaker
async def fetch_user(user_id: str) -> dict:
    async with httpx.AsyncClient(base_url="https://api.example.com", timeout=3.0, headers={"Accept": "application/json"}) as client:
        resp = await client.get(f"/users/{user_id}")
        if resp.status_code >= 500:
            raise httpx.HTTPError(f"server error: {resp.status_code}")
        if resp.status_code >= 400:
            raise ValueError(f"client error: {resp.status_code}, body={resp.text}")
        return resp.json()

async def main():
    try:
        user = await fetch_user("42")
        print(user)
    except CircuitBreakerError as e:
        print("circuit open", e)
    except Exception as e:
        print("request failed", e)

if __name__ == "__main__":
    asyncio.run(main())
  1. HTTP/2 + mTLS(任意)+ リトライ(Go)
package main

import (
  "context"
  "crypto/tls"
  "crypto/x509"
  "fmt"
  "io"
  "log"
  "net"
  "net/http"
  "time"
  "os"
  "errors"
)

func newClient() *http.Client {
  tr := &http.Transport{
    DialContext: (&net.Dialer{
      Timeout:   3 * time.Second,
      KeepAlive: 30 * time.Second,
    }).DialContext,
    TLSHandshakeTimeout: 3 * time.Second,
    MaxIdleConns:        100,
    IdleConnTimeout:     90 * time.Second,
    ForceAttemptHTTP2:   true,
  }
  if os.Getenv("MTLS") == "1" {
    cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
    if err != nil { log.Fatal(err) }
    caCert, err := os.ReadFile("ca.pem")
    if err != nil { log.Fatal(err) }
    caPool := x509.NewCertPool()
    caPool.AppendCertsFromPEM(caCert)
    tr.TLSClientConfig = &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: caPool}
  }
  return &http.Client{Transport: tr, Timeout: 5 * time.Second}
}

func getWithRetry(ctx context.Context, url string, max int) ([]byte, error) {
  client := newClient()
  var last error
  baseDelay := 200 * time.Millisecond
  for i := 0; i < max; i++ {
    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    req.Header.Set("Accept", "application/json")
    resp, err := client.Do(req)
    if err != nil {
      last = err
    } else {
      defer resp.Body.Close()
      if resp.StatusCode >= 200 && resp.StatusCode < 300 {
        return io.ReadAll(resp.Body)
      }
      if resp.StatusCode >= 500 {
        last = fmt.Errorf("server error %d", resp.StatusCode)
      } else {
        return nil, fmt.Errorf("client error %d", resp.StatusCode)
      }
    }
    d := time.Duration(i+1) * baseDelay
    select {
    case <-time.After(d):
    case <-ctx.Done():
      return nil, errors.New("context canceled")
    }
  }
  return nil, last
}

func main() {
  ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
  defer cancel()
  body, err := getWithRetry(ctx, "https://api.example.com/health", 3)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(string(body))
}
  1. OAuth2 Client Credentials + WebClient(Java/Spring)⁶
package com.example.integration;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.*;
import org.springframework.security.oauth2.client.web.reactive.function.client.*;
import org.springframework.web.reactive.function.client.*;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class App {
  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  @Bean
  public ReactiveClientRegistrationRepository clientRegistrations() {
    ClientRegistration reg = ClientRegistration.withRegistrationId("api")
      .tokenUri("https://auth.example.com/oauth/token")
      .clientId("client-id")
      .clientSecret("client-secret")
      .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
      .build();
    return new InMemoryReactiveClientRegistrationRepository(reg);
  }

  @Bean
  public WebClient webClient(ReactiveOAuth2AuthorizedClientManager manager) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(manager);
    oauth.setDefaultClientRegistrationId("api");
    return WebClient.builder()
      .filter(oauth)
      .baseUrl("https://api.example.com")
      .defaultHeader("Accept", "application/json")
      .filter(ExchangeFilterFunction.ofResponseProcessor(resp -> {
        if (resp.statusCode().is5xxServerError()) {
          return Mono.error(new RuntimeException("Upstream 5xx"));
        }
        return Mono.just(resp);
      }))
      .build();
  }

  @Bean
  public ReactiveOAuth2AuthorizedClientManager manager(ReactiveClientRegistrationRepository repo, ReactiveOAuth2AuthorizedClientService svc) {
    ReactiveOAuth2AuthorizedClientProvider provider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
      .clientCredentials()
      .build();
    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager m = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(repo, svc);
    m.setAuthorizedClientProvider(provider);
    return m;
  }
}
  1. Webhook署名検証 + 冪等ガード(Node.js/Express)¹¹¹⁰
import express from 'express';
import crypto from 'crypto';
import bodyParser from 'body-parser';

const app = express();
app.use(bodyParser.raw({ type: 'application/json' }));

function verifySignature(secret, payload, signature) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(payload).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || '', 'utf8'));
}

app.post('/webhooks/vendor', (req, res) => {
  const sig = req.get('X-Signature');
  const raw = req.body.toString('utf8');
  if (!verifySignature(process.env.WEBHOOK_SECRET || 'dev', raw, sig)) {
    return res.status(401).send('invalid signature');
  }
  try {
    const event = JSON.parse(raw);
    // TODO: event.id をKVに記録し重複処理を防ぐ
    console.log('event', event.type);
    res.status(202).send('accepted');
  } catch (e) {
    return res.status(400).send('bad payload');
  }
});

app.listen(3000, () => console.log('listening on :3000'));
  1. 簡易ベンチマーク(autocannon、Node.js)
import autocannon from 'autocannon';

const url = process.argv[2] || 'http://localhost:3000/health';
autocannon({
  url,
  connections: 100,
  duration: 20,
  pipelining: 1,
  headers: { 'Connection': 'keep-alive' }
}, (err, result) => {
  if (err) {
    console.error(err);
    process.exit(1);
  }
  console.log({
    rps: result.requests.average,
    p95: result.latency.p95,
    errors: result.errors
  });
});
  1. OpenTelemetryで分散トレース(Node.js)¹²
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }),
  instrumentations: [getNodeAutoInstrumentations()]
});

sdk.start().then(() => {
  console.log('OTel started');
}).catch((e) => {
  console.error('OTel error', e);
  process.exit(1);
});

パフォーマンスと信頼性: 計測・最適化の実務

計測指標(SLI/SLO候補)

  • 可用性: 成功率(2xx/3xx)≥ 99.9%
  • レイテンシ: p95 ≤ 200ms(外部向け)、内部gRPCはp95 ≤ 50ms
  • スループット: 目標RPSに対しCPU使用率≤70%での安定動作
  • エラー率: 5xx ≤ 0.1%、タイムアウト≤0.2%

参考ベンチマーク(ローカル、M2/32GB、Node18、Go1.22、HTTP keep-alive)

シナリオRPS(avg)p95(ms)エラー率
Keep-Alive無効(HTTP/1.1)8501200.2%
Keep-Alive有効(HTTP/1.1)2,300620.1%
HTTP/2(同一ホスト)3,100480.1%
レスポンスキャッシュ(ETag/304)3,600380.05%

観点と対策

  • コネクション再利用: Keep-Alive/HTTP2でRTTを削減。
  • 再試行ポリシー: 冪等操作に限定。指数バックオフ+ジッタでスパイク回避。⁷
  • タイムアウト分割: コネクション/応答/全体。クライアントとゲートウェイで多層防御。
  • キャッシュ: ETag/Last-Modified/Cache-Controlで不要呼び出しを減らす。
  • バルクヘッド/Circuit Breaker: 依存先障害の伝搬を遮断。⁸
  • 可観測性: OTelでtraceparentを外部APIコールまで連携、失敗点を1ホップで特定。¹²

ビジネス効果(ROIモデル)

  • ROI = (削減工数×人件費/期間 + 障害削減コスト) − 導入/運用コスト
  • 例: 再試行・冪等実装で障害対応を月8h削減、時給8,000円、12ヶ月→768,000円。Gatewayのレート制限でSLA逸脱罰金ゼロ化。初期実装120h、運用/月5hなら、初年度純効果は十分にプラスになりやすい。
  • 導入期間目安: 設計1〜2週、PoC1週、実装2〜4週、計測/調整1〜2週(計5〜9週)。

導入手順とガバナンス

実装手順

  1. 目的とSLOの定義: 例) 外部注文APIのp95≤200ms、成功率≥99.9%。
  2. 契約の固定化: OpenAPI/Protobufでスキーマ合意、互換性ルール(後方互換、非推奨期間)を明文化。⁵
  3. 認証・認可の選定: 外部はOAuth2/OIDCとスコープ、内部はmTLS+短命JWT。⁶
  4. 通信戦略: 外部REST、内部gRPC/HTTP2、GraphQLはBFFに限定。³⁵
  5. 信頼性パターン: 冪等キー、再試行+バックオフ、Circuit Breaker、Rate Limit、バルクヘッドをクライアントに組み込む。⁷⁸¹⁰⁹
  6. セキュリティ: 署名付きWebhook、秘密はVault/KMSで管理、証明書自動ローテーション。¹¹
  7. 可観測性: OTel導入、トレースIDをログ/メトリクスに紐付け、ダッシュボードとアラート設定。¹²
  8. パフォーマンス検証: 負荷試験(autocannon/k6)、p95/p99、エラー率、GC/スレッドを観測。
  9. ロールアウト: 段階的リリース、カナリア/フェイルセーフ、SLO逸脱時の即時ロールバック基準。
  10. 運用/改善: SLIレビュー、エラー分類、レート/タイムアウト更新、コスト最適化(帯域/CPU/外部課金)。

契約テストとバージョニング

  • Producer/Consumerテスト(例: Pact)で破壊的変更を抑止。OpenAPI Diffで差分を機械検出。⁵
  • 非互換変更はメジャー、後方互換変更はマイナー。非推奨は通知→猶予→削除の3段階。

セキュリティの要点

  • OAuth2は最小権限スコープ、トークンは短寿命+ピンニング。WebhookはHMAC署名検証+リプレイ防止(タイムスタンプ/nonce)。¹¹⁶
  • mTLSはCI/CDで自動発行と失効、秘密はアプリに埋め込まない。レート制限はIP/トークン/ルート単位で多層化。⁹

運用のベストプラクティス

  • エラーバジェット管理で改善施策の優先度を制御。SLO逸脱時は機能追加より信頼性改善を優先。
  • Runbook整備(429/401/5xx/タイムアウト)と当番体制。依存先のステータスページ/メンテナンスカレンダーを監視に統合。
  • コスト観測(RPS×外部課金、帯域、egress)。キャッシュ命中率とRPSの相関で費用対効果を継続評価。

まとめ(次のアクション) API連携は用語の正確な理解と契約主導の開発が成否を分ける。定義を揃え、冪等性・再試行・断路・署名検証・可観測性を標準化すれば、信頼性と速度は同時に達成できる。自社のSLOと依存先のSLAを見直し、最小のPoCでp95とエラー率を測り、契約テストとOpenTelemetryを導入してほしい。次に着手するのは、レート制限とリトライのポリシー統一、そしてベンチマーク基準の共有だ。どのユースケースから改善を始めれば、事業インパクトが最大化されるだろうか。

参考文献

  1. Postman. State of the API 2023: APIs and Monetization. https://www.postman.com/state-of-api/2023/apis-and-monetization/
  2. Postman. State of the API 2023. https://www.postman.com/state-of-api/2023/apis-and-monetization/#:~:text=When%20asked%20whether%20their%20APIs,of%20the%20business%27s%20total%20revenue
  3. GraphQL Specification (October 2021). https://spec.graphql.org/October2021/
  4. GraphQL.org. DataLoader. https://graphql.org/blog/dataloader/
  5. OpenAPI Specification. https://swagger.io/specification/
  6. IETF RFC 6749: The OAuth 2.0 Authorization Framework. https://www.rfc-editor.org/rfc/rfc6749
  7. AWS Prescriptive Guidance. Retry with exponential backoff. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html
  8. Microsoft Docs. Implement the Circuit Breaker pattern. https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/implement-circuit-breaker-pattern
  9. Stack Overflow. Correct client reaction to HTTP 429 in multi-threaded clients. https://stackoverflow.com/q/53457432
  10. Stripe Docs. Idempotent requests. https://docs.stripe.com/api/idempotent_requests
  11. Stripe Docs. Verify webhook signatures and prevent replay attacks. https://docs.stripe.com/webhooks?verify=verify-manually#:~:text=Additionally%2C%20verify%20webhook%20signatures%20to,libraries%2C%20or%20%2047%20verify
  12. CNCF Blog. OpenTelemetry demystified: a deep dive into distributed tracing. https://www.cncf.io/blog/2023/05/03/opentelemetry-demystified-a-deep-dive-into-distributed-tracing/