API呼び出し設計のデザインパターン - RepositoryFactory
こんにちは。HANOWAエンジニアの佐々木です。
今回はHANOWAで導入したAPIの呼び出し設計、RepositoryFactoryパターンについてお話しさせていただきます。
RepositoryFactoryパターンとは
Vueエヴァンジェリスト(ITの啓蒙家的なもの)のJorgeという方によって2018年に紹介されたAPI呼び出しのデザインパターンです。
フロント側からサーバー側のAPIを呼び出し、データの登録やデータの取得を行いますが、フロント側からAPIを呼び出す部分をいい感じにできる設計のうちの一つです。
名前の通りRepositoryパターンとFactoryパターンの2つのデザインパターンを組み合わせた考え方です。
HANOWAでの導入経緯
HANOWAでは今までpagesやstoreから思いのままaxiosでエンドポイントを指定してAPIを実行していました。同じAPIの呼び出しが2、3箇所に点在しているような状態でした。
そのような状態だとエンドポイントが変更された場合にAPIを呼び出している各ファイルを修正する必要があり、リファクタリングや再利用も難しいコードになってしまいます。
APIの数が少ない場合はこのままでも良いかもしれませんが、HANOWAでは厳しくなっていたのでAPI呼び出し設計の検討を始めました。
初めはapi.tsファイルを作成して、API呼び出しをまとめようと考えていましたが、先輩エンジニアよりRepositoryFactoryを教えていただき、導入するに至りました。
ディレクトリ構成を考える
HANOWAでは既に@nuxtjs/axiosを使用しており追加でライブラリ等のインストールは不要なので、ディレクトリ構成から考えていきます。
src
├ factories
│ └ apiRepositoryFactory.ts
├ plugins
│ └ api.ts
└ repositories
├ hoge
│ ├ profileRepository.ts
│ └ postRepository.ts
└ fuga
├ userRepository.ts
└ postRepository.ts
結論上記のような構成になりました。axiosの設定ファイルは既にsrcディレクトリ外のpluginsディレクトリにあったので今回はそのまま使用することにしましたが、srcのpluginsに含めてしまったほうがまとまりがあって良いかもしれませんね。
repositoryを作成する
次にrepositoryの中身を作成していきます。
ここは使用するAPIをエンドポイントごとにまとめる感じになります。
今回はエンドポイントに合わせてディレクトリを切りrepositoryファイルを配置していきます。
// src/repositories/hoge/profileRepository.ts
import type { NuxtAxiosInstance } from '@nuxtjs/axios';
const resource = '/hoge/profile-repository';
export const ProfileRepository = ($axios: NuxtAxiosInstance) => ({
get(params: GetProfileParams) {
return $axios.$get(`${resource}`, params);
},
update(id: string) {
return $axios.post(`${resource}/${id}`, {
headers: {
Authorization: `Bearer ${authToken}`,
}
});
},
});
factoryを作成する
作成したrepositoriesを一元管理するfactoryを作成していきます。
// src/factories/apiRepositoryFactory.ts
import { ProfileRepository } from '@/src/repositories/hoge/profileRepository';
export interface Repositories {
profile: typeof ProfileRepository;
}
const repositories: Repositories = {
profile: ProfileRepository
};
export const apiRepositoryFactory = {
get: (key: keyof Repositories) => repositories[key],
};
FactoryをNuxtにインジェクトする
作成したFactoryを呼び出すためにNuxtにインジェクトしておくことで、vue,tsファイルからthis.$repositoriesで呼び出すことができます。
// src/plugins/api.ts
import { Inject, NuxtApp } from '@nuxt/types/app';
import {
apiRepositoryFactory,
Repositories,
} from '@/src/factories/apiRepositoryFactory';
export default ({ app }: { app: NuxtApp }, inject: Inject) => {
const repositories = (name: keyof Repositories) => {
return apiRepositoryFactory.get(name)(app.$axios);
};
inject('repositories', repositories);
};
実際にvueファイルからAPIを叩いてみる
<script>
export default {
data() {
return {
id: 1,
phoneNumber: 00011112222
};
},
methods: {
async getProfileItems() {
try {
await this.$repositories('profile').get(
{
profile_id: this.id
phone_number: this.phoneNumber,
}
);
} catch(e) {
console.error(e)
}
},
},
};
</script>
まとめ
今回はRepositoryFactoryパターンの導入について説明いたしました。
まだ全てのAPIをRepositoryFactoryに移行しきれておりませんが、保守しやすくなったと思います。
少し戸惑った点としてFactoryにRepositoryを格納する際の命名でした。API側の設計によってはrepositoriesの名前が同じになる可能性もあると思います。(hode/postRepository.tsとfuga/postRepository.tsが存在する)
そういった場合は以下のような記載にいたしましたが、命名規則に関してはプロジェクト内で事前に決めておくと良さそうです。
import { HogeProfileRepository } from '@/src/repositories/hoge/profileRepository';
import { FugaProfileRepository } from '@/src/repositories/fuga/profileRepository';
export interface Repositories {
'hoge/profile': typeof HogeProfileRepository;
'fuga/profile': typeof FugaProfileRepository;
}
const repositories: Repositories = {
'hoge/profile': HogeProfileRepository,
'fuga/profile': FugaProfileRepository,
};
HANOWAでは他のメンバーもnoteを書いているので、ぜひ見に行ってみてください!
この記事が気に入ったらサポートをしてみませんか?