【フォースタ テックブログ】RepositoryFactoryパターンをVueのAPIリクエストに導入する - for Startups Tech blog Nuxt.js × typescriptで実装する api repositoryFactoryパターン | スマートショッピング 【Vue.js】Web API通信のデザインパターン (個人的ベストプラクティス) - Qiita
RepositoryFactoryパターンとは
APIを呼び出す設計のデザインパターンとして、JorgeというVueエヴァンジェリストによって紹介された。 Vue API calls in a smart way
- Repositoryによって、axiosインスタンスをコンポーネントに直接書くのを避けることができる
- データの操作をビジネスロジックと分離する
- エンドポイントの変更に強くなる
- 再利用性が高まる
- Factoryによって、各ケースで必要なリポジトリをインスタンス化する
- コンポーネントはRepositoryの実体化の方法を知らなくていい
- テストのためのmockがしやすくなる
準備
import { Context } from '@nuxt/types'
import { Inject, Plugin } from '@nuxt/types/app'
import {
UserRepository,
} from '~/repositories/api'
/**
* APIリクエストのインターフェース
* グローバルに$repositoriesで呼び出すことができる
* unit testのときはrepositoriesを差し替えることでmock可能
*/
export interface RepositoryApis {
user: UserRepository
}
const plugin: Plugin = ({ $axios, $config }: Context, inject: Inject) => {
const user = new UserRepository($axios)
const repositories: RepositoryApis = {
user,
}
inject('repositories', repositories)
}
export default plugin
import { NuxtConfig } from '@nuxt/types'
const config: NuxtConfig = {
// ...
plugins: [
'~/plugins/repository-api',
],
}
export default config
import { NuxtAxiosInstance } from '@nuxtjs/axios'
import { Consola } from 'consola'
import { Store } from 'vuex'
import { RepositoryApis } from '~/plugins/repository-api'
// axios, consola は関係ないが使うので入れておくとしたらこんな書き方
declare module 'vue/types/vue' {
export interface Context {
$axios: NuxtAxiosInstance
$logger: Consola
$repositories: RepositoryApis
}
interface Vue {
$logger: Consola
$repositories: RepositoryApis
}
}
declare module '@nuxt/types' {
export interface Context {
$axios: NuxtAxiosInstance
$logger: Consola
$repositories: RepositoryApis
}
interface NuxtAppOptions {
$logger: Consola
$repositories: RepositoryApis
}
}
declare module 'vuex/types' {
interface Store<S> {
$repositories: RepositoryApis
}
}
import type { AxiosInstance } from 'axios'
export class UserRepository {
private readonly axios: AxiosInstance
constructor($axios: AxiosInstance) {
this.axios = $axios
}
async search(id: string): Promise<User> {
// this.$axios.get みたいな処理
}
}
使い方
Vueコンポーネント内で $repositories
で使用できる
async fetchUser(id: string) {
const data = await this.$repositories.user.search(id)
return data
}
テスト
import { MockProxy, mock } from 'jest-mock-extended'
import { Context } from '@nuxt/types'
import { RepositoryApis } from '~/plugins/repository-api'
import { UserRepository } from '~/repositories/api'
describe('user', () => {
let mockContext: MockProxy<Context>
let repositories: MockProxy<RepositoryApis>
beforeEach(() => {
const mockUser = mock<UserRepository>()
repositories = mock<RepositoryApis>()
repositories.user = mockUser
mockContext.$repositories = repositories
})
it('do something', () => {
doSomething(mockContext)
expect(repositories.user.search).toHaveBeenCalledTimes(1)
expect(repositories.user.search).toHaveBeenCalledWith({
id: 'foo',
})
})
})