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 API | HTTP/1.1/2、JSON | 汎用CRUD、B2B連携 | 冪等性、HTTPステータス、キャッシュ制御 |
GraphQL | クライアント主導のクエリ言語³ | HTTP/WS | 集約クエリ、複数ソース統合 | N+1対策、スキーマガバナンス⁴ |
gRPC | バイナリRPC | HTTP/2、Protobuf | 低レイテンシ、内部マイクロサービス | 双方向ストリーミング、IDL契約 |
OpenAPI | API契約仕様⁵ | 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 | 双方向TLS | TLS | ゼロトラスト | 証明書ローテ、秘密管理 |
Observability | 可観測性¹² | OTel/OTLP | 分散トレーシング | トレース連携、SLI設計¹² |
設計判断の軸は、データ量・対話性・レイテンシ要件・信頼境界・スキーマの進化速度を基準に選ぶ。例えば内部間はgRPC、外部公開はREST+OpenAPI、集約はGraphQLという住み分けが一般的だ。³⁵認証は「外部=OAuth2、内部=mTLS+短命トークン」⁶、スロットリングはゲートウェイで集中管理、再試行はクライアント内で冪等操作に限定する⁷。
実装パターンとコード例(完全版)
- 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;
}
})();
- 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())
- 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))
}
- 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;
}
}
- 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'));
- 簡易ベンチマーク(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
});
});
- 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) | 850 | 120 | 0.2% |
Keep-Alive有効(HTTP/1.1) | 2,300 | 62 | 0.1% |
HTTP/2(同一ホスト) | 3,100 | 48 | 0.1% |
レスポンスキャッシュ(ETag/304) | 3,600 | 38 | 0.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週)。
導入手順とガバナンス
実装手順
- 目的とSLOの定義: 例) 外部注文APIのp95≤200ms、成功率≥99.9%。
- 契約の固定化: OpenAPI/Protobufでスキーマ合意、互換性ルール(後方互換、非推奨期間)を明文化。⁵
- 認証・認可の選定: 外部はOAuth2/OIDCとスコープ、内部はmTLS+短命JWT。⁶
- 通信戦略: 外部REST、内部gRPC/HTTP2、GraphQLはBFFに限定。³⁵
- 信頼性パターン: 冪等キー、再試行+バックオフ、Circuit Breaker、Rate Limit、バルクヘッドをクライアントに組み込む。⁷⁸¹⁰⁹
- セキュリティ: 署名付きWebhook、秘密はVault/KMSで管理、証明書自動ローテーション。¹¹
- 可観測性: OTel導入、トレースIDをログ/メトリクスに紐付け、ダッシュボードとアラート設定。¹²
- パフォーマンス検証: 負荷試験(autocannon/k6)、p95/p99、エラー率、GC/スレッドを観測。
- ロールアウト: 段階的リリース、カナリア/フェイルセーフ、SLO逸脱時の即時ロールバック基準。
- 運用/改善: 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を導入してほしい。次に着手するのは、レート制限とリトライのポリシー統一、そしてベンチマーク基準の共有だ。どのユースケースから改善を始めれば、事業インパクトが最大化されるだろうか。
参考文献
- Postman. State of the API 2023: APIs and Monetization. https://www.postman.com/state-of-api/2023/apis-and-monetization/
- 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
- GraphQL Specification (October 2021). https://spec.graphql.org/October2021/
- GraphQL.org. DataLoader. https://graphql.org/blog/dataloader/
- OpenAPI Specification. https://swagger.io/specification/
- IETF RFC 6749: The OAuth 2.0 Authorization Framework. https://www.rfc-editor.org/rfc/rfc6749
- AWS Prescriptive Guidance. Retry with exponential backoff. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html
- Microsoft Docs. Implement the Circuit Breaker pattern. https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/implement-circuit-breaker-pattern
- Stack Overflow. Correct client reaction to HTTP 429 in multi-threaded clients. https://stackoverflow.com/q/53457432
- Stripe Docs. Idempotent requests. https://docs.stripe.com/api/idempotent_requests
- 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
- 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/