もともと想定していなかった使い方ではあったけれど、Vuex、そしてVuexのプラグインであるVuex ORMを使って、マイクロサービスなアプリにおけるデータの面白いリレーション管理ができた。
一体全体どういうことなのかというのを書いてみる。何かの参考になれば幸いだ。
マイクロサービスの構成
マイクロサービスというと、システムやアプリが必要とする機能(サービス)を、細かく分けて構築する、例えば認証を担当するサービス、記事を管理するサービス、通知を管理するサービス、といった具合に。もちろん、一口にマイクロサービスといっても定義はまちまちだし、特に構成となると千差万別だが、少なくとも大まかなコンセプトはこういうことで大きく間違ってはいないはず。
まずは、今回僕が作った構成を説明する。繰り返しになるが、これが「あるべきマイクロサービスの姿」などと言うつもりは全くない。あくまで今回作ったのがこうだった、と言うだけだ。
- Front-end – Nuxt で作られたフロントエンド。
- Auth / Proxy Service – 認証、およびAPI Gateway。
- Resources Service – 記事などを管理するサービス。
- Form Service – 投稿などを管理するサービス。
- etc.
といった具合だ。基本的に、Front-endは(2)のAuth / Proxyサーバに向かってリクエストを投げると、認証を行なった後、そのリクエストに対応したサービスへとディスパッチされる。認証機能とAPI Gatewayを分ける構成もよく見られるが、ここでは特に影響もないので1つにした。
また、各サービスは言語・フレームワークが異なってもよく、サービスごとに別のDBを持っている。重要なのはこのサービス毎に別のDBを持っていると言う点だ。
例えば、Authサービスはusers
やroles
テーブルを持っている。Resourcesサービスはposts
やtags
を持っている。それぞれ別々のコンポーネントなので、Resourcesサービスはユーザ情報などにアクセスできない状態だ。
とはいえ、記事には著者が存在するため、APIとしては、Postの情報と、そのPostにひもづくユーザ情報を返す、という形になるかと思う。と言うことは、API Gatewayがその「結合」作業を行うのか、はたまた別のところでやるのかはさておき、ともかくそう言う事が必要になる。
リレーションをFront-endで組み立てる
さて、今回Front-endでVuex ORMを採用したが、このライブラリはリレーションを持ったデータを分解(Normalize)してVuexのStoreに保存し、Vuex Gettersを使って再度組み立てたり、フィルターしたりするものだ。Pluginの詳細はこちらの記事を参照。
これを使うと、ちょっと面白い事ができる。ここではシンプルにResourcesサービスから記事を取得して、表示するという状況を考える。
まず、Resourcesサービスは、自身のDBに記事と、その記事に紐づくユーザのIDだけを保持している。APIからのレスポンスはこんな感じだ。
[
{ id: 1, user_id: 1, title: '...' },
{ id: 2, user_id: 1, title: '...' },
{ id: 3, user_id: 2, title: '...' }
]
さて、画面にはユーザ(記事の著者)の名前も表示したい。ので、普通に考えるとこういうレスポンスが欲しい。
[
{ id: 1, user_id: 1, title: '...', user: { name: 'John Doe' } },
{ id: 2, user_id: 1, title: '...', user: { name: 'John Doe' } },
{ id: 3, user_id: 2, title: '...', user: { name: 'Jane Doe' } }
]
今回のマイクロサービスだと、誰がこのユーザ情報を記事に紐づけるのか? という問題がある。ところが、今回Vuex ORMを利用しているため、このリレーション管理をFront-endで行う事ができる。コードで書くとこんな感じだ。
// Back-endからデータを取得。
const users = await Api.get('/api/users')
const posts = await Api.get('/api/posts')
// Vuex ORMを使ってデータをVuexに保存。
store.dispatch('entities/users/insert', { data: users })
store.dispatch('entities/posts/insert', { data: posts })
// Vuex ORMを使ってリレーションを組み立てた状態でデータを取得。
const postsWithUsers = store.getters['entities/posts']().with('users').get()
/*
[
{ id: 1, user_id: 1, title: '...', user: { name: 'John Doe' } },
{ id: 2, user_id: 1, title: '...', user: { name: 'John Doe' } },
{ id: 3, user_id: 2, title: '...', user: { name: 'Jane Doe' } }
]
*/
もともとこういう事がしたくて作っていたわけではないのだが、意識せずやってたらこんな事ができていた。やりやすいなと感じたのは、バックエンドは本当に自分のサービスのデータにだけ集中できる、という点だ。複数のレスポンスをいい具合に組み合わせる新たなBack-endのサービスも必要ない。
もっとも、あらゆるシステムにおいてこれが最善とは思わない。例えばユーザが100人程度いるだけで、全ユーザを予め取得してなんてやっていくのもおかしい。Front-endから投げるリクエストも2回に増えるという話もある。今回僕が作ったものではこれで良かったというだけで、全てのサービスをこうするべきという話ではない。
最後に
Vuex ORMはマイクロサービスのために作られた、という訳でもないけれど、こういう使い方もできるんだなという事が知っていただければ幸いだ。データ構造によってはちょっとしたものをこういう風にFront-endで組み立てるというのはなかなか爽快な場面もある。もし興味があれば試してみて欲しい。