はじめに
REST APIを使ってWEBアプリケーションを作成する際は、多くのユーザー数からの大量アクセスを想定しているかと思います。
その為、特に使用頻度の高い機能の実装をする場合、リクエストで取得したデータはキャッシュでクライアント側である程度管理、保持することで、同じデータを何度もサーバーから呼び出すことを節約する考え方も大切になってきます。
@redux/toolkitをはじめとするステート管理ライブラリでは、ミドルウェアを自作することで、 高頻度で同じAPIを叩いてしまう状態を防ぐ事が可能となります。
特にDBから大量のリストを取得するような機能には、長期運用時のコストが雲泥の差になる為、導入したいですね。
![]()
(引用:いらすとや)
そもそも年間どれくらい節約できるの?
それでは、ユーザー数が5万人、 1ユーザーあたり1日20回のAPIリクエストが発生する、APIリクエスト1回あたりのコスト0.01円、リクエスト削減率90% という仮定に基づいて、年間の節約額を計算してみましょう。
- 1ユーザーあたりの1日あたりのAPIリクエスト削減数: 20回 * (1 - 1/10) = 18回
- 1ユーザーあたりの1日あたりのコスト削減額: 18回 * 0.01円/回 = 0.18円
- 1ユーザーあたりの年間コスト削減額: 0.18円/日 * 365日/年 = 65.7円/年
- 5万人ユーザーの年間コスト削減額: 65.7円/年/ユーザー * 50,000ユーザー = 3,285,000円/年
この仮定に基づくと、年間のコスト削減額は約328万5千円と試算されます。
年間で300万円以上のコスト削減が見込めるというのは、非常に大きな効果と言えるでしょう。
↑300万円って札束にするとこれくらいです(引用:いらすとや)
実際の削減額は、アプリケーションの利用状況やインフラコストによって変動します。より正確な数値を把握するためには、実際のAPIリクエスト数を計測し、状態管理ライブラリ導入後の効果測定を行うことが重要です。
状態管理ライブラリへのミドルウェアカスタムによるリクエストの節約は、コスト削減だけでなく、アプリケーションのパフォーマンス向上や開発効率の改善にも繋がる可能性があります。今回の試算が、導入を検討する上での一助となれば幸いです。
実装手順
@redux/toolkitを使って高頻度で同じAPIを叩いてしまう状態を防ぐ機能を実現する場合、以下のような実装フローが考えられます。
- createAsyncThunkを使ってAPIリクエストを行う
- createEntityAdapterを使ってデータの管理を行う
- createSliceを使ってストアを定義する
- createApiを使ってAxiosのラッパーを作成し、リクエストのキャッシュ管理を行う
まず、APIリクエストを行うcreateAsyncThunkを定義します。
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetchData = createAsyncThunk(
'data/fetchData',
async (_, { getState }) => {
const { data } = await axios.get('/api/data');
return data;
}
);
次に、createEntityAdapterを使ってデータの管理を行います。
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { fetchData } from './actions';
const dataAdapter = createEntityAdapter();
const dataSlice = createSlice({
name: 'data',
initialState: dataAdapter.getInitialState(),
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchData.fulfilled, (state, action) => {
dataAdapter.upsertMany(state, action.payload);
});
}
});
export const { selectAll: selectAllData } = dataAdapter.getSelectors(
(state) => state.data
);
最後に、createApiを使ってAxiosのラッパーを作成し、リクエストのキャッシュ管理を行います。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const dataApi = createApi({
reducerPath: 'dataApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getData: builder.query({
query: () => '/data',
transformResponse: (response) => response.data,
providesTags: ['Data'],
keepUnusedDataFor: 60 // 1 minute
})
})
});
export const { useGetDataQuery } = dataApi;
これらを組み合わせて使うことで、高頻度で同じAPIを叩いてしまう状態を防ぐ機能を実現できます。
- fetchDataアクションを使ってAPIリクエストを行い、データをdataSliceに保存します。
- useGetDataQueryを使ってデータを取得し、キャッシュの有効期限を設定できます。
- 必要に応じて dataApi.util.invalidateTags(['Data']) を呼び出してキャッシュをクリアできます。
このようにして、高頻度で同じAPIを叩いてしまう状態を防ぐ機能を @redux/toolkit を使って実現できます。
おわりに
いかがでしたでしょうか?
仕事というのはお金を意識してこそ成り立つものです。プロとして実装を任されている以上、お客様のニーズに応え、運用を想定したささやかな仕掛けが、後にエンジニアとしての実力の差を生みます。