Nuxt.js

【Nuxt.js × TypeScript】Repository Factoryパターン

マナビト
マナビト
こんにちはマナビトです。
普段はフロントエンドエンジニアとしてNuxt.js/vue.js/TypeScriptを
中心に開発業務を行なっています。

今回は、最近学んだRepository Factoryパターンについて記事にしていきます。
Web APIを呼び出す際に、単体テストやリファクタリングがしやすくなり、プロジェクトを進めていく上で、採用を検討しても良いデザインパターンかと思います。

Repository Factoryパターンとは

Repository FactoryパターンとはWeb APIを呼び出すために設計されたデザインパターンのことを指します。2018年にJorge氏のブログによって紹介されました。

このブログでは、APIを呼び出す際にコンポーネント内にaxiosインスタンスを直書きしてAPIを呼び出しているコードがしばしばみられ、そのように記載されたコードの問題点を指摘しています。

  • 単体テストをする場合はどうするか
  • APIの呼び出し処理を再利用したい場合にどうするか
  • VuexのActions処理を移植することができない
  • リファクタリングが困難

この問題に対処するためにデザインされたのが、Repository Factoryパターンです。その名の通り、RepositoryパターンとFactoryパターンを組み合わせた設計パターンとなります。
Repositoryパターンはドメインオブジェクト(データアクセス)を抽象化する設計手法のことで、DDDで提唱されました。Factoryパターンはインスタンスの作り方をスーパークラスで定め、具体的な処理をサブクラスで行うパターンのことを指し、オブジェクトの生成と具体的な処理を分離することが可能です。

Repositoryパターン

Repositoryパターンとはドメイン毎に分けてデータアクセスを1箇所に集約するRepositoryを作成し、APIにアクセスするロジックをコンポーネントから分離する方法です。

Factoryパターン

一方でFactoryパターンはインスタンスの生成口を一元管理するFactoryを作成し、インスタンスの生成ロジックを分離する方法です。

これらRepository + Factoryを組み合わせることで呼び出しや生成ロジックが分離されている為、拡張性や共通化を行うことができ、リファクタリンが容易になります。コードのメンテナンス性が上昇しDRYの原則を保つことが可能です。

Nuxt.jsにおける実装

それではNuxt.jsで実際にRepository Factoryパターンに沿った実装をしてみたいと思います。

ドメイン毎にデータにアクセスする処理をRepositoryに集約するファイルを作成します。APIのリクエストはこのファイルからのみ行われます。

import { NuxtAxiosInstance } from '@nuxtjs/axios'
import { AxiosResponse, AxiosInstance } from 'axios'

export type Book = {
  id: number
  title: string
  mail: string
}

export type BookRepository = {
  get: () => Promise<AxiosResponse<{ shops: Book[] }>>
  findById: (id: number) => Promise<AxiosResponse<Book>>
}

export const BooksRepository = (
  $axios: NuxtAxiosInstance | AxiosInstance
): BookRepository => ({
  get: () => {
    return $axios.get(`/books`)
  },
  findById: (id) => {
    return $axios.get(`${id}`)
  },
})

次にFactoryパターンを生成します。

import { UserRepository } from '../repositories/userRepository'
import { BooksRepository } from '../repositories/booksRepository'

export interface Repositories {
  user: typeof UserRepository
  books: typeof BooksRepository
}

const repositories: Repositories = {
  user: UserRepository,
  books: BooksRepository,
}

export const apiRepositoryFactory = {
  get: (name: keyof Repositories) => repositories[name],
}

Factryを作成して、Repositoryのインスタンスを生成します。どのRepositoryを生成するかはFactoryが決定できるようにします。

pluginフォルダにaxiosの設定を記載します。

import { NuxtApp } from '@nuxt/types/app'

const baseUrl = 'http://localhost:3000'

export default ({ app }: { app: NuxtApp }) => {
  app.$axios.setBaseURL(baseUrl)

  app.$axios.onRequest((config) => {
    console.log('request >>>', config)
  })

  app.$axios.onResponse((config) => {
    console.log('response >>>', config)
  })

  app.$axios.onError((e) => {
    if (!e.response) return

    console.log(e.response)
  })
}

また、pluginフォルダに追加でinjectを記載して、関数を共通化させておきます。これでuseContext()から$repositoriesでFactoryを呼び出すことが可能になります。

import { Inject, NuxtApp } from '@nuxt/types/app'

import {
  apiRepositoryFactory,
  Repositories
} from '@/composables/factories/apiRepositoryFactory'

export default ({ app }: { app: NuxtApp }, inject: Inject) => {
  const repositories = (name: keyof Repositories) => {
    return apiRepositoryFactory.get(name)(app.$axios)
  }

  inject('repositories', repositories)
}

最後に、コンポーネントにaxiosの処理をRepositoryへのメソッド呼び出しを行うように記載します。

<script lang="ts">
import { defineComponent, useContext, useAsync } from '@nuxtjs/composition-api'

export default defineComponent({
  setup() {
    const { app } = useContext()

    const books = useAsync(async () => {
      const response = await app.$repositories('books').get()
      return response.data
    })

    return {
      books,
    }
  },
})
</script>

まとめ

今回はRepository Factoryパターンを意識した実装について紹介していきました。APIの呼び出しコードをコンポーネントに直書きするのではなく、呼び出し元を集約して記載することでメンテナンス性が向上し、拡張性の高い実装を行うことができるかと思います。

■ 参考文献