APIの整理とお掃除をしました　開発合宿での成果を紹介

これは[ユアマイスター Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yourmystar)の16日目の記事です。

# はじめに
先日、開発チームで合宿を行いました。
詳細はこちらの記事をご覧下さい。

<img width="1440" alt="スクリーンショット 2019-12-15 16.49.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267738/d2746291-2cc0-bf4a-8daa-54d604478640.png">
[「今年の汚れ今年のうちに」合宿2019に行ってきました](https://yourmystar-engineer.hatenablog.jp/entry/2019/12/11/080000)

その時にやったことを紹介しようと思います。

# 前提
## 各種バージョン
- PHP
    - 7.1.32
- CakePHP
    - 3.6.14

# やったこと
- *API系Controllerの整理*

ひとえに**整理**と言っても、何のことか全く分からないと思うので、もう少し詳細に記載すると・・・

- *API系Controllerの仕様の再定義*
- *ルーティングの整理*
- *親クラスの作成*
- *テストコードの作成*

です。

詳細を以下で紹介します。

# 現状の課題
- API専用Controllerや、通常のController内にAPI用メソッドがあったりと、API系のControllerが煩雑な状態になってる

これが１番の課題で、その他でいうと、

- 同一のロジックが記述されてる
- 品質の担保がされていない

などの課題もありました。

# アプローチ
## 其の１：仕様の再定義
まず、煩雑な状態を解消するために、API系のControllerの仕様を再定義しました。

決定事項としては、

**API系のControllerは、独立した専用のControllerを用意し、処理を記述するようにする**

です。

決定に当たった要因としては、

- 役割が明確
- コスパの良い改修で事が済む

の2点でした。

独立した専用のControllerを用意する事で、間違いなく役割や用途はハッキリします。
ここが明確になる事で、以降の管理や開発で、直感的に作業ができるため、開発効率アップに繋がるでしょう。

また、限られた時間でいかに効率の良い効果のある改善を行うか、を考えた時に、コストパフォーマンスは重要なポイントの１つでした。


## 其の２：ルーティングの整理
方向性は決まったので、次はルーティングの整理を行いました。

ここで私が課題だと感じてた事は、APIが増えるにつれてルーティング用ファイル（routes.php）の中身が**肥大化する事**です。

肥大化する事で、**HTTPリクエストに対するURLの名前解決に要する時間が増え、HTTPレスポンスを返すまでの時間が増加するのではないか**、が未来に考えられる負債だと私は思いました。

つまり、**肥大化を抑え、一元管理できるような仕組みを作りたい**、が私のやりたい事です。

結果、２つのことをやりました。

### 1. ディレクトリ構成の変更

```terminal
// ディレクトリ構成

$ tree src/Controller
src/Controller
├── XxxController.php
├── Api
│   └── YyyController.php
├── ZzzController.php
```

**Api**というディレクトリをControllerディレクトリ配下に用意し、そのApiディレクトリ配下にApi系のControllerを置くようにしました。

これは後述する、ルーティングの整理のためなんですが、この構成を取ることでルーティングの整理だけではなく、どこになんのControllerがあるのか、が直感的にわかるようになったため、開発効率のアップに繋がります。

### 2. プレフィックスルーティングの利用

```php
// routes.php

 1 use Cake\Core\Plugin;
 2 use Cake\Routing\RouteBuilder;
 3 use Cake\Routing\Router;
 4 use Cake\Core\Configure;
 5 
 6 Router::defaultRouteClass('DashedRoute');
 7 
 8 Router::prefix('api', function ($routes) {
 9     $routes->fallbacks();
 10 });
```

少しコードの解説をします。

まず、 *L:6* でルーティングのセットアップをします。
引数に指定した値によって、受け付けるURLのケースが変わります。

- **DashedRoute**
    - Controller, Action共に**チェインケース**
    - 例：
        - Controller：HogeHogeController
        - Action：sampleIndex
        - URL：/hoge-hoge/sample-index/

- **InflectedRoute**
    - Controllerは**スネークケース**、Actionは**アッパーキャメルケース**
    - 例：
        - Controller：HogeHogeController
        - Action：sampleIndex
        - URL：/hoge_hoge/sample_index/

- **Route**
    - Controller, Action共にそのまま
    - 例：
        - Controller：HogeHogeController
        - Action：sampleIndex
        - URL：/HogeHoge/sampleIndex/

*L:8* 以降は、プレフィックスルーティングの処理です。
prefixメソッドの第一引数に、URLの先頭に付くプレフィックスの文字列を指定します。

第二引数でコールバックメソッドを利用して、fallbacksメソッドを利用してます。
fallbacksメソッドは、定義されたルーティングのルールを参照してURLの名前解決を行ってくれるメソッドですが、引数無しで呼ぶことで、defaultRouteClassメソッドで定義されたルーティングのルールを参照します。
ここでは、DashedRouteを定義してるので、**/hoge-hoge/sample-index/**のURLパターンで名前解決してくれます。

プレフィックスルーティングと、fallbacksメソッドを利用して、API系のURLパターンを**/api/hoge-hoge/sample-index/**としました。

ルーティングの整理をし、URL構造を明確に定義したことで、随分とすっきりとした状態にする事ができました。

## 其の３：親クラスの作成
現状の課題でも話したように、あちこちで同一の処理を記述してたため、共通化を第一の目的にAPI用の親クラスを作成しました。

共通化が第一の目的ではありますが、冗長なコードを除くことでコードの可読性アップや、ファイルの肥大化を避ける意味合いも含まれているため、親クラスの作成は有効的だったと思います。

以下がその親クラスです。

```php
  1 <?php
  2 
  3 namespace App\Controller;
  4 
  5 use Cake\Event\Event;
  6 use App\Controller\AppController;
  7 
  8 /**
  9  * Api Controller
 10  *
 11  *
 12  * @method \App\Model\Entity\Api[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
 13  */
 14 class ApiController extends AppController
 15 {
 16     ...
 17 }
```

実際のロジック部分は記述してませんが、このような感じで、親クラスを作成し、各API系のControllerは、このApiControllerを継承するようにしました。


## 其の４：テストコードの作成
こちらも同じく現状の課題で少しお話ししたように、品質の担保がなされていない状態だったため、最低限の品質担保を狙いとしてテストコードを書く事としました。

```php
  1 <?php
  2 
  3 /**
  4  * App\Controller\HogeHogeController Test Case
  5  */
  6 class HogeHogeControllerTest extends IntegrationTestCase
  7 {
  8     /**
  9      * setUp method
 10      *
 11      * @return void
 12      */
 13     public function setUp()
 14     {
 15         parent::setUp();
 16     }
 17 
 18     /**
 19      * tearDown method
 20      *
 21      * @return void
 22      */
 23     public function tearDown()
 24     {
 25         parent::tearDown();
 26     }
 27 
 28     public function testSampleIndex()
 29     {
 30         $this->get('/api/hoge-hoge/sample-index/');
 31         $this->assertResponseCode(200);
 32         $this->assertContentType('json');
 33         $this->assertSame('UTF-8', $this->_response->getCharset());
 34         ...
 35     }
```

こちらも一部抜粋で記載しますが、基本的には、HTTPステータスコードや型、文字コードなどの最低限のチェックを記述してます。

# まとめ
APIの整理と題して色々紹介した訳ですが、いかがだったでしょうか？

結果として、仕様の定義やロジックの共通化、テストコードなど、当初思い描いていた自分のやりたい事は全て実現できました。
もう少しコードレベルで最適解を追求する事はできそうですが、一旦、理想系に近い状態になったの良しとします。やりながら最適を求めたいと思います。

また、良かった事でいうと、自分で問題の提起から行い、自分で解決策を模索し、自分で解決に至った、この経験です。
もちろん、その過程の中には周囲の方に相談して方向性を決める事はありましたが、最終的には、自分で決め自分で実行する訳なので、この経験は本当に良かったと思います。

ひたすら開発に専念できる時間と環境が整った開発合宿は非常に有意義な時間でした。