Vuex
Firestore
+
2018-08-03 pixiv社内勉強会
@hakatashi
Vuex
Vuex
- Vue.jsでglobalなステートを管理するための
デザインパターン+ライブラリ - 要するにflux
Firestore
- Firebaseの一機能として提供される
データベース - Firebase Realtime Database の後継
- 2017年10月に発表されて以来
未だにベータ版
Firestore
- クライアント-サーバー間で
リアルタイムに同期可能なNoSQL - Realtime Database と比較して
データ構造がだいぶまともに - リアルタイム性とスケーラビリティのために
いろんなものが犠牲になっている
Vue+Firestoreで
できること
Vue+Firestoreで
できること
-
Firestoreでデータベース上のデータが
クライアントに同期される -
Vue.jsでクライアントのステートが
DOMにバインディングされ同期される - →リモートのデータベースの内容が
何もしなくてもクライアントのViewに
同期される!!!!!
デモ
Firestoreの考え方
- クライアントが直接DBを読み書きする
- レコード・テーブルに対するアクセス権限が
細かく設定できるので、基本はこれで対処可能 - より複雑なこと (複雑なトランザクション・外部リソースに依存するデータ検証・etc) をやりたい場合は
Cloud Function にやらせる
書き込み
同期
Firestore
Function
複雑なこと
HTTPリクエスト
Firestoreの利点👍
- リアルタイム同期
- オフラインキャッシュ
- 公式のSDKが優秀
- レコードに対するアクセスコントロール
-
無限にスケールする!
- レコード数が1000万件とかになった時に
破綻するようなクエリはそもそも
最初から書けないようになっている (後述)
- レコード数が1000万件とかになった時に
Firestoreの欠点👎👎👎👎👎👎👎👎👎
- 重い
- cold start 問題 (最初のデータ取得に数秒かかる)
-
クエリが貧弱
- SQLやNoSQLで当然のようにできていたことは
何一つとしてできない - Firestoreでできないこと: NOT, !=, OR句, UNIQUE制約, IN, LIKE, RANDOM, EXISTS, MIN(), MAX(), COUNT(), SUM(), JOIN全般 (inner join, outer join), UNION, GROUP BY, $all, $elemMatch, $size, $type
- SQLやNoSQLで当然のようにできていたことは
逆に何ができるの
- 実行可能なクエリ =
- 複数のフィールドに対する == 演算
- + 単一のフィールドに対する >=, >, <, <= 演算
Firestore的考え方をする
- 最初にDB構造を設計する時点でこのあたりを
最大限考慮しないといけない- 「正規化をやめることを考える」
- データ構造の選択 | Firebase - 「カウンターを別に持つ」
- 分散カウンタ | Firebase - 「配列ではなくオブジェクトのキーとしてデータを持つ」
- 配列、リスト、セットの操作 | Firebase - 「テキスト検索は外部サービスで持つ」
- 全文検索 | Firebase
- 「正規化をやめることを考える」
VuexとFirestoreを繋ぐ
“VuexFire”が便利
DBの/counter/counterをVuexの$store.state.counterにbindする
import {firebaseAction, firebaseMutations} from 'vuexfire';
const counterRef = db.collection('counter').doc('counter');
export default new Vuex.Store({
state: {
counter: null,
},
mutations: {
...firebaseMutations,
},
actions: {
bindCounter: firebaseAction(async ({bindFirebaseRef}) => {
await bindFirebaseRef('counter', counterRef);
}),
},
});
bind
使い方
<template>
<p>Count: {{counter}}</p>
</template>
<script>
export default {
data() {
return { initialized: false };
},
computed: {
...mapGetters(['counter']),
},
mounted() {
this.$store.dispatch('bindCounter').then(() => {
this.initialized = true;
});
},
destroyed() {
this.$store.dispatch('unbindCounter');
},
};
</script>
例えば、Vue Component のmount時にbindして、destroy時にunbindする
コレクション内の
必要なデータのみbindする
export default new Vuex.Store({
state: {
data: {},
},
actions: {
bindById: firebaseAction(async ({bindFirebaseRef, state}, id) => {
// 二重bindを防ぐ
if (state.data[id]) {
return;
}
const couplingRef = couplingsRef.doc(id);
await bindFirebaseRef(`data.${id}`, couplingRef);
}),
},
});
オブジェクトの形でデータを持つ (賛否両論)
Vuexfireには二重bindを防ぐ機構がないので注意が必要
関連データを同時にbindする
actions: {
bindCharacter: firebaseAction(async ({bindFirebaseRef, dispatch}, {name}) => {
// 二重bindを防ぐ (省略)
const characters = await charactersRef.where('name', '==', name).get();
const character = characters.docs[0];
const characterBindPromise = bindFirebaseRef(`data.${character.id}`, character.ref);
const couplings = await couplingsRef.where(`members.${character.id}`, '==', true).get();
await Promise.all([
characterBindPromise,
...couplings.docs.map((coupling) => (
dispatch('couplings/bind', coupling.id, {root: true})
)),
]);
}),
}
bindと同時にクエリを実行し、全部bindしたらresolveする
UI上でどんどんデータが降ってくる様子が見えるのでエモい (?)
SSR
export default {
async fetch({store, params}) {
if (!process.browser) {
await store.dispatch('characters/bindCharacter', {
name: params.name,
});
}
},
...
};
Nuxt.jsならfetchメソッドに突っ込めば一発
ただgenerateのたびにDBにアクセスして重いのでなんとかしたい
Vuex+Firestoreに対する所感
- Vuexfireはまだ未熟な部分が多い
- nuxt generate などと組み合わせれば、
サーバーレスでDBを持ってSSRまでできる - しかもリアルタイムで同期できるのはエモい
- ただ、すべてをFirestoreに乗せるのは
データ構造上しんどい- 本格的にやりたいなら
リアルタイムに同期したいデータのみ
Firestoreに乗せるのでもよいのではないか
- 本格的にやりたいなら
- Firestoreは早く正式版をリリースしろ
おしまい
Vuex+Firestore
By Koki Takahashi
Vuex+Firestore
2018-08-03 pixiv社内勉強会
- 3,872