mybook

エピローグ — APIエコシステムを育てる

1年後のサクラ

「テックブリッジAPI、先月のAPI呼び出しが1億回を超えました」

会議室に歓声が上がった。1年前、サクラが初めてRailsのAPIモードを立ち上げたときは、ゼロだった。今では50社のパートナーが、毎日数百万のリクエストを送っている。

だが、サクラは浮かれていなかった。「数字は結果。大切なのはエコシステムが健全に育っているか、だ」

Developer Experienceとは何か

Loading diagram...

DXが良いAPIは、口コミで広まる。パートナーが「このAPIは使いやすい」と言うとき、それは技術的な正確さではなく、体験全体を評価している。

Onboarding体験の設計

5分でHello Worldを達成させる

# クイックスタート
 
## 1. APIキーを取得する(30秒)
1. https://dashboard.techbridge.jp にアクセス
2. 「新規登録」→「APIキーを発行」
 
## 2. 最初のリクエストを送る(1分)
```bash
curl https://api.techbridge.jp/v2/users \
  -H "X-API-Key: sk_live_YOUR_KEY_HERE"

3. レスポンスを確認する

{
  "data": [...],
  "meta": { "pagination": { "total_count": 42 } }
}

これで完了です。あとはチュートリアルで詳しく学べます。


### インタラクティブなサンドボックス

```ruby
# サンドボックス環境の構築
class SandboxController < ApplicationController
  # サンドボックス専用の軽量データセット
  before_action :enforce_sandbox_limits

  def enforce_sandbox_limits
    # サンドボックスは読み取り専用、データは固定
    if request.post? || request.patch? || request.delete?
      render json: {
        message: "サンドボックスモードでは読み取りのみ可能です。本番APIキーで試してください",
        sandbox: true
      }
      return
    end
  end
end

INFO

Stripeのように、実際のクレジットカードを使わずにテストできるサンドボックス環境を用意することで、パートナーが安心して統合テストを行えます。

SDKの提供

APIを直接叩くのではなく、SDKを提供することでDXが大幅に向上する。

# techbridge-ruby SDK例
# gem 'techbridge-sdk'
 
client = Techbridge::Client.new(api_key: "sk_live_xxx")
 
# シンプルな一覧取得
users = client.users.list(page: 1, status: "active")
users.each { |u| puts u.name }
 
# ページネーションの自動処理
client.users.all.each do |user|
  # 全ページを自動的に取得
  puts user.name
end
 
# エラーハンドリング
begin
  user = client.users.create(name: "", email: "invalid")
rescue Techbridge::ValidationError => e
  e.errors.each { |err| puts "#{err.field}: #{err.message}" }
rescue Techbridge::RateLimitError => e
  puts "制限中。#{e.retry_after}秒後に再試行"
  sleep(e.retry_after)
  retry
end

SDK自動生成

OpenAPI定義からSDKを自動生成する。

# openapi-generatorを使ったSDK生成
npx @openapitools/openapi-generator-cli generate \
  -i swagger/v2/swagger.yaml \
  -g ruby \
  -o sdk/ruby \
  --additional-properties=gemName=techbridge-sdk
 
# Python SDK
npx @openapitools/openapi-generator-cli generate \
  -i swagger/v2/swagger.yaml \
  -g python \
  -o sdk/python
 
# TypeScript SDK
npx @openapitools/openapi-generator-cli generate \
  -i swagger/v2/swagger.yaml \
  -g typescript-fetch \
  -o sdk/typescript

パートナープログラムの設計

Loading diagram...
# app/models/partner.rb
class Partner < ApplicationRecord
  enum tier: { free: 0, standard: 1, pro: 2, enterprise: 3 }
 
  TIER_LIMITS = {
    free: { monthly_calls: 1_000, rate_limit_per_minute: 60 },
    standard: { monthly_calls: 100_000, rate_limit_per_minute: 300 },
    pro: { monthly_calls: 1_000_000, rate_limit_per_minute: 1_000 },
    enterprise: { monthly_calls: -1, rate_limit_per_minute: 10_000 }
  }.freeze
 
  def monthly_call_limit
    TIER_LIMITS[tier.to_sym][:monthly_calls]
  end
 
  def approaching_limit?
    return false if monthly_call_limit == -1  # 無制限
    current_month_calls > monthly_call_limit * 0.8
  end
end
 
# 80%使用時にアラートメール
class PartnerAlertJob < ApplicationJob
  def perform
    Partner.find_each do |partner|
      if partner.approaching_limit?
        PartnerMailer.usage_alert(partner).deliver_later
      end
    end
  end
end

フィードバックループの構築

# APIフィードバックの収集
class ApiFeedbackController < ApplicationController
  def create
    feedback = ApiFeedback.create!(
      partner: @current_partner,
      endpoint: params[:endpoint],
      rating: params[:rating],  # 1-5
      comment: params[:comment],
      api_version: params[:api_version]
    )
 
    # Slackに通知
    if feedback.rating <= 2
      SlackNotifier.post_to_channel(
        "#api-feedback",
        "低評価フィードバック from #{@current_partner.name}: #{feedback.comment}"
      )
    end
 
    head :created
  end
end

API変更のコミュニケーション

# app/mailers/partner_mailer.rb
class PartnerMailer < ApplicationMailer
  def api_change_notice(partner, change)
    @partner = partner
    @change = change
    @breaking = change.breaking_change?
 
    subject = if @breaking
      "[重要] テックブリッジ API #{change.version} 破壊的変更のお知らせ"
    else
      "テックブリッジ API #{change.version} リリースノート"
    end
 
    mail(to: partner.technical_contact_email, subject: subject)
  end
end
 
# views/partner_mailer/api_change_notice.text.erb
件名: <%= mail.subject %>
 
<%= @partner.name %> ご担当者様
 
テックブリッジAPIに変更があります。
 
バージョン: <%= @change.version %>
リリース日: <%= @change.released_at.strftime("%Y年%m月%d日") %>
 
<% if @breaking %>
⚠️ この変更には後方互換性のない変更が含まれます。
移行期限: <%= @change.sunset_date.strftime("%Y年%m月%d日") %>
<% end %>
 
変更内容:
<%= @change.description %>
 
移行ガイド: https://docs.techbridge.jp/migration/<%= @change.version %>

API Analytics

# APIの使われ方を分析してプロダクト改善に活かす
class ApiAnalyticsReport
  def most_used_endpoints(period: 30.days)
    ApiLog.where(created_at: period.ago..)
          .group(:endpoint)
          .order(count_all: :desc)
          .count
  end
 
  def error_rate_by_endpoint(period: 24.hours)
    ApiLog.where(created_at: period.ago..)
          .group(:endpoint)
          .having("AVG(CASE WHEN status_code >= 500 THEN 1.0 ELSE 0.0 END) > 0.01")
          .average("CASE WHEN status_code >= 500 THEN 1.0 ELSE 0.0 END")
  end
 
  def partner_growth
    Partner.group("DATE_TRUNC('month', created_at)")
           .count
           .transform_keys { |k| k.strftime("%Y-%m") }
  end
 
  def generate_report
    {
      period: "直近30日",
      total_api_calls: ApiLog.where(created_at: 30.days.ago..).count,
      unique_partners: ApiLog.where(created_at: 30.days.ago..).distinct.count(:partner_id),
      top_endpoints: most_used_endpoints,
      error_endpoints: error_rate_by_endpoint
    }
  end
end

長期的なAPI戦略

Loading diagram...

APIガバナンス

# APIライフサイクル管理
class ApiLifecycleManager
  LIFECYCLE_STAGES = %i[experimental beta stable deprecated sunset].freeze
 
  def promote_to_stable(endpoint)
    endpoint.update!(
      lifecycle_stage: :stable,
      stability_guaranteed_until: 18.months.from_now
    )
 
    # パートナーに通知
    Partner.all.each do |partner|
      PartnerMailer.endpoint_stable_notice(partner, endpoint).deliver_later
    end
  end
 
  def start_deprecation(endpoint, sunset_date:)
    endpoint.update!(
      lifecycle_stage: :deprecated,
      sunset_date: sunset_date
    )
 
    # 移行ガイドを作成するまで廃止プロセスを止める
    raise "移行ガイドが必要です" unless endpoint.migration_guide.present?
 
    Partner.using_endpoint(endpoint).each do |partner|
      PartnerMailer.deprecation_notice(partner, endpoint).deliver_later
    end
  end
end

WARNING

APIの廃止は最後の手段です。一度公開したAPIを廃止することは、パートナーのビジネスに直接影響します。廃止の決定は最低6ヶ月前の告知と、明確な移行パスを必ず用意してください。

サクラの旅の終わり、そして始まり

「今日でAPIプロジェクト開始から1年。どんな気持ち?」

CTOが聞いた。

サクラは少し考えてから答えた。

「APIを作ることは、世界に向けてドアを開けることだと思っていました。でも今は違う気がします」

「どういう意味?」

「ドアを作るだけじゃない。その向こうに住むコミュニティを作ること。パートナーが問題を解決できる場所を作ること。そのコミュニティが自走できるようになること」

サクラはモニターに映るAPIダッシュボードを見た。50のパートナー、1億のAPI呼び出し、0.01%のエラーレート。

「APIは技術じゃない。関係性だ」

この旅で学んだこと

原則学んだこと
一貫性予測可能なAPIはドキュメントより雄弁
後方互換性約束を守ることが信頼を作る
DX開発者体験が採用率を決める
可観測性計測できないものは改善できない
コミュニケーション変更は技術問題ではなく関係性の問題

設計チェックリスト

## パブリックAPI公開前チェックリスト
 
### 設計
- [ ] RESTful URIの命名規則
- [ ] 一貫したレスポンス形式
- [ ] バージョニング戦略
 
### セキュリティ
- [ ] 認証・認可の実装
- [ ] HTTPS強制
- [ ] レート制限の設定
- [ ] WAFの設定
 
### 品質
- [ ] リクエストスペックの充実(カバレッジ80%以上)
- [ ] N+1クエリの排除
- [ ] ヘルスチェックエンドポイント
 
### ドキュメント
- [ ] OpenAPI定義の完成
- [ ] クイックスタートガイド
- [ ] コードサンプル(主要言語)
- [ ] エラーコードリファレンス
- [ ] 変更履歴
 
### 運用
- [ ] CloudWatchアラームの設定
- [ ] SLOの定義
- [ ] インシデント対応フローの整備
- [ ] Sunset日程と移行計画の準備

「次のプロジェクトは何ですか?」

ヒロキが聞いた。

「GraphQLの本格展開と、gRPCでのマイクロサービス分割」

サクラは笑った。良いAPIを作る旅に、終わりはない。技術は進化し、ユーザーの要求も変わる。でも、基本は変わらない。

一貫性、信頼性、そして開発者への優しさ。

それがパブリックAPIを生きたエコシステムに変える。