複数ECサイトを一括検索するアプリを作ってみた。

## 作ったもの
検索キーワードを入れると、Yahoo,楽天,Amazon（まだ）の複数ECサイトで検索を行い、商品を閲覧できるアプリケーションを作成しました。「価格順」「評価数順」などの並び替えや、価格での絞り込み機能もあります。
メルカリ、ラクマ、Paypayフリマ等への検索結果へもボタンを押すと遷移することができます。
現在はアプリケーション閉じてます。。　

![スクリーンショット 2022-12-26 10.44.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/485238/65bf3813-5db9-45b3-ca65-bcd68ac38733.png)

## 作った背景
自分の中でネットショッピングがブームでして、ほとんどの買い物をオンラインでしています。
ゲームやDVD、日用品などのちょっとしたものまで、ほとんどがECサイトで買える便利な世の中になりましたね。

商品を見てから買いたいものなんて洋服、家電、家具くらいです。。

そんなオンラインショッピングを多く使う私ですが、「Amazonで絶対買い物したい！」などのこだわりがないため複数のサイトを見て回ることが多いです。

そんなオンラインショッピングでの手間を2つ解決したいと思い、オリジナルのアプリを作成しました。

### 一番安いものを買いたい
日本人なら誰でも思う感情です。
自分は買いたい商品が決まった後は、一番安いサイトで買いたいと思う民なので、各サイトにいちいち同じ商品ワードを入力して価格比較を行っていました。なんなら中古でも良いかと言う時はフリマサイトでも検索をかけていました。

今回自分が作成したアプリでは商品名での検索ができるため、どこのサイトが一番安いのか一目で判断することができます。

### 各サイトのラインナップを知りたい！
買いたいものが決まっていなくて、ざっくりとしたワードで検索をすることもあります。(例えば「スマホケース」　とか)
そんな時にもこちらのアプリで検索をかけていただければ各サイトで取り扱っている商品を一括で調べることができます。

## 使用した技術
一緒に開発してくれる友達と自分の共通でできる言語はPHPだったので、サーバーサイドはLaravel。
フロント側はお互い興味のあったVue.jsで実装し、SPA構成にしました。

## 検索機能実装
API側は検索結果を返すエンドポイントを実装していて、VueからAjaxで叩いてもらって表示をさせています。
API側はServiceにビジネスロジック、Repositoryに外部API接続処理を分ける形で実装をしました。

```
app
├　Services
  ├ RakutenService.php
  ├ YahooService.php
  └ ItemSerachService.php
└ Repositories
  ├ RakutenItemRepository.php
  └ YahooItemRepository.php
```

Repositoryのやることは単純で、リクエストを作成して各種APIを叩きます。(ざっくり載せます)
PHPで書かれたSDKがあったのでそちらを使っています。

https://github.com/rakuten-ws/rws-php-sdk

``` RakutenItemRepository.php
class RakutenItemsRepository
{
    public function __construct()
    {
        $this->client = new RakutenRws_Client();
        $this->client->setApplicationId(config('app.rakuten_id'));
        $this->client->setAffiliateId(config('app.rakuten_affiliate_id'));
    }

    public function search(string $word, $filter)
    {
        $response = $this->client->execute('IchibaItemSearch',array(
            'keyword' => $word,
            'sort' => self::SEARCH_OPTION_SORT,
            'carrier' => self::CARRIER_SMART_PHONE,
            'minPrice' => $filter['lowPrice'] === 'all' ? 0 : $filter['lowPrice']
        ));

        if(!$response->isOk()){
            $this->log->error(sprintf('code: %s message: %s', $response->getCode(), $response->getMessage()));
            $response = [];
        }
        return $response;
    }
}
```

Service層では取得したAPIレスポンスの整形や集計、計算を担当します。

``` RakutenService.php

class RakutenService implements RakutenServiceInterface
{
    public function get(string $word, array $filter)
    {
        $searchItems = $this->rakutenItemsRepository->search($word, $filter);

        $items = [];
        foreach($searchItems as $record){
            $item = new \stdClass();
            $item->name = $record['itemName'];
            $item->price = $record['itemPrice'];

            // 画像が空の場合は次の商品
            if (empty($record['mediumImageUrls'][0])) continue;

            $item->imageUrl = $this->resizeImageUrl($record['mediumImageUrls'][0]['imageUrl'], 300);
            $item->affiliateUrl = $record['affiliateUrl'];
            $item->reviewAverage = (int)$record['reviewAverage'];
            $item->reviewCount = $record['reviewCount'];
            $item->point = floor($item->price * ($record['pointRate'] / 100));
            $items[] = $item;
        }

        return $items;
    }
}
```

ItemSearchServiceでは楽天、Yahooの各種サービスで返す値をまとめてます。

各種APIにリクエスト制限があるので、それにかからないようにキャッシュを使ったロックを行っているのですが、別途別の記事で解説出来ればと思います。
また、リクエスト回数をすくなくするためにも検索結果のキャッシュも行っています。

``` ItemSearchService.php
class ItemsSearchService　
{
    public function __construct(RakutenServiceInterface $rakutenService, YahooServiceInterface $yahooService)
    {
        $this->rakutenService = $rakutenService;
        $this->yahooService = $yahooService;
    }

    public function execute(string $target, array $filter)
    {
        $list = json_decode(Cache::get('hoge'));
        if (!isset($list)) {
            // 一実行につき2秒間ロックさせる
            $lock = Cache::lock('api_request', 2);

            try {
                // APIリクエスト制限にかからないように、リクエストを同時に飛ばないようにする
                // 所定の時間待って、検索ができなかった場合はエラーにする
                $lock->block(self::WAIT_SECONDS);

                $list = array_merge([], $this->rakutenService->get($target, $filter));
                $list = array_merge($list, $this->yahooService->get($target, $filter));

                Cache::put('hoge', self::CACHE_SECONDS);
            } catch (LockTimeoutException $e) {
                $this->log->error('waiting more then '. self::WAIT_SECONDS);
                throw $e;
            } catch(Exception $e) {
                $this->log->error($e->getMessage());
                throw $e;
            }
        }

        return $list;
    }
}

```

## 今後実装したい機能
現在絶賛改良中なのですが、今後は下記部分を実装しようと思っております！

・Amazon追加。→他社と比べてAPIの審査が厳しくなっていて、審査待ちになっています。
・オートコンプリート機能→ワードの候補を常に出して、検索でのUXを上げていきたいと考えています。
・検索履歴保存
・カテゴリ検索機能

使っていただけた際には使用した感想等を載せていただけると幸いです。
引き続き改良を進めていきます！！