eaglesakuraの技術ブログ

技術的な話題とか、メモとか。

実質2日でDatastoreをFirestore移行した話

謝罪

ごめんなさいごめんなさいごめんなさいごめんなさいごめんなさいごめんなさいごめんなさいごめんなさい。

本番環境のFirestoreをONにしてしまったのは僕です。

何が起こったのか

  • アプリから利用されるサーバーをGAE/Goで開発していた
  • GCPプロジェクトは、アプリ側とサーバー側が同一プロジェクトを使用していた
    • Service Account使ったり、そっちのほうが都合が良かったから

Datastoreを利用してデータを保存していた

  • サーバーはGoogle App Engine / Go(1.9系)を使用していた
  • データは goon ライブラリを通してDatastoreに保存していた

Firestoreの扱い

  • 開発当初から使用実績のあるDatastoreを使う予定で、必要な複数のGCPプロジェクトで Datastore を選択し、なおかつ Firestore / Datastore Mode を明示的に選択していたはずだった
  • そのうちFirestore Native ModeがDatastoreと共存できるだろう、という見込みだったが、直近では使う予定がなかった
  • そもそも、現時点で FirestoreとDatastoreを同時に利用する手段 は提供されていない。なので、どちらかを使った時点でどちらかを諦めるのだ。

圧倒的速度のFirestore展開

  • アプリ開発中、怒涛の勢いでFirestoreがBlazeしてきた
    1. Firestore 日本上陸
    2. Firestore GAになる
    3. Firestore Released <- WOW!!
  • すでにサーバーサイドは開発中だったため、「うわー、すげー。けどまだ使えなーい」と思いながら様子を見ていた

圧倒的操作ミス

  • 本番環境で Firestore のコンソールにアクセスしてしまった
  • その瞬間、Firestoreは本番環境で完全に有効化されてしまった!!
  • なぜだ?Datastoreを明示的に選択していたはずなのに!?
  • Datastoreにデータがない場合、シュレディンガーのFirestoreとなってしまうようだ!!
    • まだ本番運用されていない(開発中)のため、Datastoreにデータがなかった
    • そのため、Firestoreの選択が未確定と扱われていた(不具合か仕様かはわからんけど)ようだ。

圧倒的幸運

  • 開発中のサービスであったため、本番環境にデータはない
  • プログラムの移行作業はあれど、データの移行はしなくて良い

移行作業量

  • サーバーサイドはmicro-service architectureで開発していた
  • そのなかで、Datastoreを使っていたServiceは2つだった
  • 残りはそのServiceを用いてデータアクセスするアーキテクチャだったため、移行作業はなかった

ちゃんと設計しといてよかったデータアクセスのアーキテクチャ

  • Clean ArchitectureとDDDを取り入れているアーキテクチャだった
  • サーバーサイドの主なレイヤーわけ
    1. APIハンドラ層(httpハンドリングを行う)
    2. Adapter層(ドメインAPIハンドラのオブジェクト変換)
    3. Domain 層(サーバー内でのドメインを扱うやつと、ビジネスロジックを扱うやつ)
    4. データアクセス層(ここがDatastoreにアクセスしている、依存層)
  • ドメイン層はデータアクセスに関して抽象化されており、基本的に appengine とか datastore packageとは完全に切り離していた
  • APIハンドラはDomainを受け取り、Adapterを使って実際のJSONモデル(用の構造体)に落とし込む仕組み
    • 別層から受け取るデータは基本的にinterfaceなので、規定interfaceさえ満たしていれば問題ない
  • Datastore用の構造体とJSON変換用の構造体を共用しているひとは、この時点で致命的な致命傷になってしまうぞ!
    • 個人的には、面倒でもちゃんと分けたほうが平和的な平和を手に入れられると思うぞ!
    • 構造体に json:"hoge" datastore:"hoge" が共存している案件は要注意だ!

データアクセス層の移行

  • HogeService.GetHoge(id) したとき、戻すオブジェクトは datastore格納用の構造体ではなく、ドメインのinterface だったため、影響は HogeService 類の中だけですべて封じ込めた
    • 設計した当時の俺ガンバッタ!
  • 構造体設計を変更し、構造体につけるタグを goon:"id" とかから firestore:"id" とかに変えた
    • 明示しなくても多分使えるけど、Firestoreは 将来的にモバイルからのアクセスが想定される ので、Go構造体名のママにするのは避けた
  • Query移行
    • 単純なソートやフィルタしか使ってなかったので、ほぼそのまま(コードもほとんどそのまま)移植できた
    • ラッキー!

Unit Test

  • ちゃんとテストを書いてたよ!
    • テストはほとんどブラックボックステスト(外部仕様テスト)で構築してた
    • なので、テストコードは未修正で、テスト前後の挙動が(テストを書いた範囲で)正しく一致することを速やかに確認できた
    • Index貼り忘れとかQueryのミスもコレで気づけたよ
    • たまに「本当にdatastore入ってる?」みたいなテストがあったので、それは適宜置き換えか削除

今後のために

  • FirestoreかDatastoreを選択したら、ダミーでいいから1個くらいデータを書いて完全に有効化しておこう!!
  • お兄さんとの約束だぞ!!