外部データを取得するAPIを使った開発から学んだ「自由度とパフォーマンス」のバランス
はじめに
Laravel開発を始めて外部のAPIからデータを取得する機会が増えてくると、「なぜこのAPIは使いにくいのか?」「なぜこのAPIは速いのか?」といった疑問を持つことがあります。
私自身、複数の外部APIからデータを取得するシステム開発を経験する中で、APIの設計によって開発効率や運用性が大きく変わることを実感しました。
この記事では、外部APIを使った開発経験をもとに、使いやすいと思うAPIの特徴を、Laravel開発者向けに実装例とともに解説します。
外部API活用でよくある困りごと
実際に遭遇する問題
外部APIを使ってシステムを作る際、以下のような問題に遭遇したことはありませんか?
// よくある困った例
$users = Http::get('https://api.example.com/users')->json();
// → 必要なのは名前だけなのに、プロフィール画像やメールアドレスまで取得される
$user = Http::get("https://api.example.com/users/{$id}")->json();
$posts = Http::get("https://api.example.com/users/{$id}/posts")->json();
// → ユーザーと投稿を取得するのに2回もAPIを呼ぶ必要がある
よくある問題:
-
必要なデータだけ取得できない
- 名前だけ欲しいのに全てのユーザー情報が返ってくる
- 通信量が多くなってページ表示が遅くなる
-
複数のAPIを呼ぶ必要がある
- ユーザー情報と投稿情報を別々に取得する必要がある
- 処理時間が長くなってしまう
-
検索条件が限定的
- 「アクティブなユーザーのみ」といった条件で絞り込めない
- 取得後にフィルタリングする必要がある
学んだこと:もう少し柔軟性があれば...
これらの経験から、もう少し柔軟性があったら使いやすいなーと思いました。設計次第では開発の手間も変わるかも?と思ったのです。
使いやすいAPIの3つの特徴
では、どのようなAPIが使いやすいのでしょうか?業界で広く使われている3つの設計パターンをLaravelの実装例で見てみましょう。
1. 必要なデータだけを取得できる設計
この設計では、クライアント側が必要なフィールドを指定できます。
一般的にはGraphQLなどで採用されている考え方ですが、REST APIでも同様の機能を実装できます。
// routes/api.php
Route::get('/users', [UserController::class, 'index']);
// app/Http/Controllers/Api/UserController.php
public function index(Request $request)
{
$query = User::query();
// 必要なフィールドのみ取得
if ($request->has('fields')) {
$fields = explode(',', $request->get('fields'));
// IDは必須として追加
if (!in_array('id', $fields)) {
$fields[] = 'id';
}
$query->select($fields);
}
return $query->paginate(10);
}
使用例:
// 名前とメールアドレスだけ取得
GET /api/users?fields=name,email
// 結果
{
"data": [
{"id": 1, "name": "田中太郎", "email": "tanaka@example.com"},
{"id": 2, "name": "佐藤花子", "email": "sato@example.com"}
]
}
メリット:
- 通信量の削減
- 処理速度の向上
- 必要なデータのみを明確に指定できる
2. 豊富な検索条件を指定できる設計
URLパラメータで詳細な検索条件を指定できる設計です。
一般的にはJSON:APIやODataなどで採用されている手法で、RESTful APIでよく使われます。
public function index(Request $request)
{
$query = User::query();
// フィールド選択
if ($request->has('fields')) {
$fields = explode(',', $request->get('fields'));
$query->select($fields);
}
// 検索条件
$filters = $request->get('filter', []);
foreach ($filters as $field => $value) {
// 安全な検索条件のみ許可
if (in_array($field, ['status', 'role', 'department'])) {
$query->where($field, $value);
}
}
// ソート
if ($request->has('sort')) {
$sortFields = explode(',', $request->get('sort'));
foreach ($sortFields as $field) {
if (str_starts_with($field, '-')) {
// マイナスがついている場合は降順
$query->orderBy(substr($field, 1), 'desc');
} else {
$query->orderBy($field, 'asc');
}
}
}
return $query->paginate($request->get('limit', 10));
}
使用例:
// アクティブなユーザーを名前順で取得
GET /api/users?filter[status]=active&sort=name
// 管理者を新しい順で取得
GET /api/users?filter[role]=admin&sort=-created_at
3. 関連データも一緒に取得できる設計
関連するデータも同時に取得できる設計です。
一般的にはGraphQLのIncludeディレクティブやREST APIのEager Loadingと言われる手法です。
public function index(Request $request)
{
$query = User::query();
// 基本の処理...
// 関連データの取得
if ($request->has('include')) {
$includes = explode(',', $request->get('include'));
// 安全な関連データのみ許可
$allowedIncludes = ['profile', 'posts', 'department'];
$validIncludes = array_intersect($includes, $allowedIncludes);
if (!empty($validIncludes)) {
$query->with($validIncludes);
}
}
return $query->paginate($request->get('limit', 10));
}
使用例:
// ユーザーとプロフィールを一緒に取得
GET /api/users?include=profile
// 結果
{
"data": [
{
"id": 1,
"name": "田中太郎",
"profile": {
"avatar": "avatar1.jpg",
"bio": "エンジニアです"
}
}
]
}
速度とサーバー負荷のバランスを取る方法
柔軟なAPIは便利ですが、何でもできると サーバーに負荷がかかってしまいます。ここでは実用的な対策を見てみましょう。
1. 適切な制限を設ける
// config/api.php
return [
'limits' => [
'max_fields' => 20, // 一度に取得できるフィールド数
'max_page_size' => 100, // 1ページあたりの最大件数
'max_includes' => 5, // 関連データの最大数
]
];
// app/Http/Middleware/ApiLimiter.php
public function handle($request, Closure $next)
{
$limits = config('api.limits');
// フィールド数のチェック
if ($request->has('fields')) {
$fieldCount = count(explode(',', $request->get('fields')));
if ($fieldCount > $limits['max_fields']) {
return response()->json([
'error' => 'フィールド数が上限を超えています'
], 400);
}
}
// ページサイズのチェック
$limit = $request->get('limit', 10);
if ($limit > $limits['max_page_size']) {
return response()->json([
'error' => 'ページサイズが上限を超えています'
], 400);
}
return $next($request);
}
2. キャッシュを活用する
よく使われるデータはキャッシュして高速化します。
public function index(Request $request)
{
// キャッシュキーを作成
$cacheKey = 'users:' . md5($request->getQueryString());
// キャッシュから取得を試みる
$result = Cache::remember($cacheKey, 300, function () use ($request) {
$query = User::query();
// 通常の処理...
return $query->paginate($request->get('limit', 10));
});
return $result;
}
キャッシュのポイント:
- よく使われるデータは5分程度キャッシュ
- 複雑な検索条件ほど長めにキャッシュ
- キャッシュキーにはクエリパラメータを含める
3. よく使うパターンを事前定義する
頻繁に使用されるデータ取得パターンを専用のエンドポイントとして用意します。
// よく使われるパターン専用
public function dashboard()
{
return Cache::remember('users:dashboard', 600, function() {
return User::select('id', 'name', 'status', 'last_login_at')
->with('profile:user_id,avatar')
->where('status', 'active')
->latest()
->limit(10)
->get();
});
}
まとめ
外部APIを使った開発経験から、世の中にあるAPI設計を調べて個人的にこれが良いと思ったポイントをまとめます。
使いやすさのポイント:
- 必要なフィールドだけを取得できる
- 豊富な検索・ソート条件を指定できる
- 関連データも一緒に取得できる
- わかりやすいURLパラメータ
パフォーマンスを保つポイント:
- 適切な制限値を設ける
- キャッシュを効果的に使う
- よく使うパターンは専用エンドポイントを用意する
- バリデーションでセキュリティを確保する
これらのポイントを意識することで、使いやすく高速なAPIを作ることができます。まずは小さな機能から始めて、徐々に改善していくことが大切です。
参考資料
次のステップ
この記事を読んで興味を持った方は、以下のような学習をおすすめします:
- 実際に手を動かしてみる:簡単なAPIを作って試してみる
- 外部APIを分析してみる:普段使っているAPIがどのような設計になっているか調べてみる
-
パフォーマンス測定:
php artisan make:command BenchmarkApi
でベンチマークコマンドを作成してみる
Laravel開発において、APIの設計は非常に重要なスキルです。ぜひ実践で活用してみてください!
Discussion