axios による Facade パターン

# はじめに

本題と関係ないですが、ちょっと前までNode.jsで開発していたはずが最近はPython漬けの毎日です。

フロントエンドを実装していて、データ取得する際に外部APIと通信するのにaxiosを利用されている方も多いかと思います。
１つのデザインパターンとして、Facadeパターンを考えてみました。

以降のコードはフロントエンド開発に一部Vue.jsを利用しています。

## axiosのインスタンスの生成

同じような設定は何度も行うことが無いようにaxiosのインスタンスを生成するようにします。
キャメルケースへの変換もインタセプトして行います。

```javascript:api-context.js
import Axios from 'axios';
import changeCase from "change-object-case";

// DEBUG
const isDebug = process.env.NODE_ENV !== 'production';

// axios client設定
const client = Axios.create({
  baseURL: `${process.env.API_ENDPOINT}/api`,
  headers: {
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest'
  },
  responseType: 'json',
  timeout: 5000,
});

// リクエスト時のインタセプト
client.interceptors.request.use(
  (config) => {
    if (isDebug) {
      // ログ出力とか
    }

    // リクエスト時の共通設定とか

    return config;
  },
  (error) => {
    return Promise.reject(error);
  });

// レスポンス時のインタセプト
client.interceptors.response.use(
  (response) => {
    if (isDebug) {
      // ログ出力とか
    }

    // 結果をキャメルケースに変換
    if (response.data instanceof Array) {
      response.data = changeCase.camelArray(response.data, {recursive: true, arrayRecursive: true});
    } else {
      response.data = changeCase.camelKeys(response.data, {recursive: true, arrayRecursive: true});
    }

    return response;
  },
  (error) => {
    if (isDebug) {
      // ログ出力とか
    }

    // 結果をキャメルケースに変換
    if (error.response.data instanceof Array) {
      error.response.data = changeCase.camelArray(error.response.data, {recursive: true, arrayRecursive: true});
    } else {
      error.response.data = changeCase.camelKeys(error.response.data, {recursive: true, arrayRecursive: true});
    }

    return Promise.reject(error);
  },
);

export default client;
```

## リソースへのアクセス

REAT API毎にリソースへのデータアクセスを作成します。
必要なパラメータはシンプルに外から渡すようにします。

例えば、ユーザー情報でCRUDを提供している場合

```javascript:user-dao.js
/**
 * ユーザー API
 */
export class UserDao {
  constructor(client) {
    this._client = client;
  }

  // API名称
  get apiName() {
    return 'users';
  }

  // データ取得(list)
  async getManyAsync(config) {
    return this._client.get(`/${this.apiName}/`, config);
  }
  
  // データ取得
  async getAsync(id, config) {
    return this._client.get(`/${this.apiName}/${id}/`, config);
  }

  // 登録
  async addAsync(data, config) {
    return this._client.post(`/${this.apiName}/`, data, config);
  }

  // 更新
  async updateAsync(id, data, config) {
    return this._client.put(`/${this.apiName}/${id}/`, data, config);
  }

  // 削除
  async deleteAsync(config) {
    return this._client.delete(`/${this.apiName}/${id}/`, config);
  }
}
```

## ファサードの作成

本題のファサードです。
アプリケーション側から利用するためのシンプルなAPIを提供します。

getterでDAOのオブジェクトを取得できるようにします。

```javascript:api-facade.js
import client from './api-context';
import {UserDao} from './dao/user-dao';

/**
 * API ファサード
 */
class ApiFacade {
  /**
   * ユーザー Dao
   * @returns {UserDao}
   */
  get user() {
    if (!this._userDao) {
      this._userDao = new UserDao(client);
    }

    return this._userDao;
  }
}

export default new ApiFacade();
```

## 呼び出し例

```javascript
import apiFacade from './api/api-facade';

export const actions = {
  async getUsersAsync({state, commit}) {
    // 条件設定
    const config = {
      params: {
        xxxxx: state.xxxxx,
      },
    };

    // ユーザー情報取得
    return apiFacade.user.getManyAsync(config)
      .catch((err) => {
        commit('xxxxxx', err);
      });
  },
}
```

## まとめ

axiosを利用してAPIへのアクセスをFacadeパターンで実装してみました。
リソースや機能が増えてくるとそこで何が提供されているのか見えづらくなってくるので、
ロジックを分離し、Facadeを利用することで使う側はよりシンプルにどんなリソースがありどんな機能が
提供されているのかを把握しやすくなるのではないでしょうか。

実装方法はチームやプロジェクトによって、色々あると思います。
axiosを利用されている方の参考になれば幸いです。