複数のAPIデータを統合!生データ保存からジャンル分け、マイグレーションの苦闘と学んだこと

自作MVC

複数の情報源からデータを集約し、それを使いやすい形に整理するのは、ウェブ開発における共通の課題です。特に、動画コンテンツサイトのように多岐にわたるジャンルや出演者情報を扱う場合、その複雑さは増します。今回は、私が3つの異なるAPIから商品データを取得し、サイトに統合するまでの道のり、特に**「APIの生データの保存」「各ジャンルへの仕分け」「マイグレーションの構築とインスタンスの難しさ」**に焦点を当ててお話しします。

プロジェクトの始まり:なぜ複雑なデータ統合が必要だったのか?

私のプロジェクトの目的は、複数の大手APIプロバイダーから動画コンテンツのデータを集約し、ユーザーが快適に検索・閲覧できる統一されたプラットフォームを構築することでした。各APIはそれぞれ独自の強みを持つ一方で、データ形式や提供される情報の粒度が異なり、一筋縄ではいきません。

例えば、あるAPIは詳細なジャンル情報を提供するけれど、別のAPIはシンプルなカテゴリ分けしかしない、といった具合です。これらをどう統合し、ユーザーにとって一貫性のある情報として提供するかが最初の大きな課題でした。

挑戦1:APIの「生データ」をどう保存・活用するか?

APIから取得したデータは、必ずしもサイトで直接利用できる形ではありません。将来的な仕様変更やデバッグ、データの再利用を考慮すると、取得した**「生データ(raw JSON/XML)」**をそのままデータベースに保存することが非常に重要だと判断しました。

私の場合は、raw_api_data というテーブルを用意し、APIソース、API固有のID、そして取得した生のJSONデータをそのまま格納しました。

SQL

CREATE TABLE raw_api_data (
    id INT AUTO_INCREMENT PRIMARY KEY,
    api_source VARCHAR(50) NOT NULL, -- 例: 'SOKMIL', 'DMM'
    api_product_id VARCHAR(255) NOT NULL,
    raw_json_data JSON, -- JSON形式で生データを保存
    migrated BOOLEAN DEFAULT FALSE, -- 処理済みかどうかのフラグ
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE (api_source, api_product_id)
);

このアプローチの利点は、後からデータ構造の変更があっても、生データから再処理できる点です。しかし、欠点としては、データを加工してメインテーブルに投入する「マイグレーション」のプロセスが不可欠になることでした。

挑戦2:複雑なジャンル・メーカー・女優への「仕分け」と重複問題

生データを保存したら、次に必要になるのが、サイトで表示するための正規化されたデータへの変換と分類です。特に大変だったのが、**ジャンル、メーカー、レーベル、監督、女優といったエンティティへの「仕分け」**でした。

各APIはこれらの情報を異なる名前で提供したり、表記揺れがあったりします。例えば、「ルーブル」というメーカーが、あるAPIでは v_louvre と、別のAPIでは Louvre といったAPI IDで提供されることがあります。

ここで直面したのは**「重複の検知と既存レコードの利用」**です。

--- DEBUG: エンティティ ('ルーブル', v_louvre) の重複を検知。既存レコードを検索します。
--- DEBUG: 既存のエンティティID: 10 を発見 (API ID経由) ---
--- DEBUG: メーカー 'ルーブル' (API ID: v_louvre) の内部ID: 10。

上記は実際のデバッグログの抜粋ですが、このように、新しいデータを取り込む際に既存のメーカーやジャンルがすでに登録されていないかをチェックし、もしあればその既存のIDを利用するようにしました。これにより、データベースに同じエンティティが重複して登録されるのを防ぎ、データの整合性を保つことができました。

この処理には、データベースのUNIQUE制約と、PHP側でのSELECTINSERT IGNOREを組み合わせたgetOrCreateEntityのような共通関数が非常に役立ちました。

挑戦3:マイグレーションの構築と「インスタンスの難しさ」

今回のシステムの中核をなすのが、生データからメインのproductsテーブルへデータを移行するマイグレーションスクリプトです。これは単なるデータコピーではなく、前述の「仕分け」処理や、欠損データの補完(例:画像URLがない場合のデフォルト画像設定)も担います。

特に難しかったのが、このマイグレーションスクリプトが**「スタンドアロンで動作するCLIコマンド」として機能しつつ、「既存のデータベース接続やモデルクラスの機能を利用する」**というバランスでした。

例えば、FrontControllerProductModelのインスタンスを生成してデータを取り扱うのは容易ですが、CLIから実行されるマイグレーションスクリプトが、そのコントローラーの文脈の外で、ProductModelDatabaseクラスのインスタンスを適切に取得・利用する部分です。

私の場合は、BaseMigrateCommandという共通の基底クラスを作成し、そこでデータベース接続($this->pdo)を確立するようにしました。そして、各APIのマイグレーションコマンド(例:MigrateSokmilProducts.php)はそれを継承することで、PDOインスタンスを共有し、重複した接続処理を書かずに済むようにしました。

PHP

// BaseMigrateCommand.php (抜粋)
abstract class BaseMigrateCommand
{
    protected PDO $pdo;
    // ...

    public function __construct(string $apiSource)
    {
        // データベース接続の確立
        $this->pdo = Database::getInstance()->getConnection();
        // ...
    }
    // ...
}

// MigrateSokmilProducts.php (抜粋)
class MigrateSokmilProducts extends BaseMigrateCommand
{
    public function __construct()
    {
        parent::__construct('SOKMIL'); // 親コンストラクタでPDOインスタンスを取得
    }

    public function handle(): void
    {
        // ここで $this->pdo を使ってデータベース操作が可能
        // ...
        $this->productModel->getAllNewArrivalsByPage($itemsPerPage, $offset); // ProductModelもPDOインスタンスを渡して初期化
        // ...
    }
}

これにより、マイグレーションスクリプトはデータベースにアクセスでき、getOrCreateEntityのような共通のデータ処理メソッドも利用できるようになりました。この**「依存性の注入(Dependency Injection)」**の考え方は、PHPアプリケーションの構造化において非常に重要だと実感しました。

ページネーションの苦戦と解決、そして得られたもの

そして、全てのデータが揃った後、トップページでページネーションが機能しないという問題に直面しました。これは、フロントエンドからのページ番号の受け渡し、バックエンドでのLIMITOFFSETを使ったデータ取得、そして総ページ数の計算という一連のプロセスが正しく連携していなかったためです。

特に、ProductModelgetAllNewArrivalsByPage(int $limit, int $offset)getTotalProductsCount(): intといった、ページングを意識したメソッドを追加する必要がありました。この際、メソッドが重複して定義されているというCannot redeclareエラーにも遭遇しましたが、冷静にエラーメッセージを読み解き、不要な重複定義を削除することで解決できました。


まとめ:困難を乗り越えて見えてきたこと

3つのAPIからのデータ統合は、生データの保存から複雑なジャンル分け、そしてそれらを支えるマイグレーションの構築に至るまで、多くの技術的な課題を伴いました。特に、重複データの効率的なハンドリングや、CLIコマンド環境でのインスタンス管理は、設計段階での見落としがちな落とし穴でした。

しかし、これらの困難を一つずつ解決していく過程で、データベース設計の重要性、エラーハンドリングとデバッグのスキル、そしてコードのモジュール化と再利用性の概念を深く理解することができました。

現在、私のサイトは、複数のAPIから取得した商品を、適切なカテゴリに分類し、ページネーションも完璧に機能する状態でユーザーに提供できています。この経験は、今後の開発プロジェクトにおける大きな自信となるでしょう。

投稿者プロフィール

bicstation
AIアシスタントとの協業が、この奮闘記を可能にした
実は、今回一連の記事を執筆し、そして開発を進める上で、強力な「相棒」の存在がありました。それが、私のような開発者をサポートしてくれるAIアシスタントです。

PHPの難解なエラーログに直面した時、記事の構成がなかなか思いつかなかった時、あるいはブログのテーマに合ったアイキャッチ画像が必要だった時など、数々の場面でAIに相談し、助けを借りました。

例えば、「レンタルサーバーでのphp.ini設定の難しさ」や「.envファイルの問題」といった、私が実体験で感じた課題を伝えると、AIは瞬時にその技術的な背景や影響を整理し、ブログ記事として読者に伝わりやすい文章の骨子を提案してくれました。また、記事のテーマに合わせたアイキャッチ画像も、具体的な指示を出すだけで瞬時に生成してくれたおかげで、コンテンツ作成のスピードが格段に向上しました。

AIは完璧ではありませんが、まさに「もう一人の自分」のように、アイデアの壁打ち相手になったり、膨大な知識の中から必要な情報を引き出してくれたり、私の思考を整理する手助けをしてくれたりします。一人で抱え込みがちな開発の課題も、AIと対話することで、新たな視点や解決策が見えてくることが多々ありました。

このブログを通じて私の奮闘記を共有できているのも、AIアシスタントの存在なくしては成し得なかったでしょう。これからも、AIを賢く活用しながら、開発と情報発信を続けていきたいと思います。

\ 最新情報をチェック /

コメント

PAGE TOP
タイトルとURLをコピーしました