🔥

Firestoreドキュメントにパス情報を含めるか?

2023/01/29に公開

はじめに

業務でCloud Firestoreを扱っています。なお、サーバサイドの開発言語はGo言語です。
今回はFirestoreドキュメントにドキュメントへのパス情報を含めた場合のコードを紹介します。

想定するデータモデル

まずデータモデルを定義してみます。

  • データへのパス
/users/{{uid}}/books/{{bookId}}
  • データ
{
  title:        "xxxxxx",  // 本のタイトル
  description:  "xxxxxx",  // 本の内容
}

Go言語での実装について

次にサーバサイドでの実装について紹介します。

// ユーザが所持する本
type UsersBook struct {
	UID         string `firestore:"uid"`    // ドキュメントへのパス情報①
	BookID      string `firestore:"bookId"` // ドキュメントへのパス情報②
	Title       string `firestore:"title"`
	Description string `firestore:"description"`
	// その他のデータが続く
}

ドキュメントへのパス情報を持っているので次のメソッドが定義できます。

func (ub UsersBook) Path() string {
	return fmt.Sprintf("users/%s/books/%s", ub.UID, ub.BookID)
}

上記のメソッドが汎用的に使えるようにインターフェースとして定義し、firestoreへのSet/Get関数を記述します。

type Pathable interface {
	Path() string
}

func Set(ctx context.Context, f *firestore.Client, data Pathable) error {
	_, err := f.Doc(data.Path()).Set(ctx, data)
	return err
}

func Get(ctx context.Context, f *firestore.Client, data Pathable) error {
	snapshot, err := f.Doc(data.Path()).Get(ctx)
	if err != nil {
		return err
	}
	return snapshot.DataTo(data)
}

サンプルコード

データを1件読み込んで、編集後に書き込む場合の処理は以下になります。

func readWrite(uid, bookID string) {
	// データ格納用変数を作成し、パスに関する情報のみ埋める
	book := UsersBook{
		UID:    uid,
		BookID: bookID,
	}

	// データを読み込む
	_ = Get(ctx, f, &book)

	book.Title = "xxxx"

	// データ書き込み
	_ = Set(ctx, f, &book)
}

クエリで一覧を取得する

本方式での魅力はクエリで取得するドキュメントの一覧がシンプル(別途パス情報を持つ変数を持たなくて良い)になることです。

func List[T any](ctx context.Context, q firestore.Query) ([]T, error) {
	var ret []T
	iter := q.Documents(ctx)
	snapshots, err := iter.GetAll()
	if err != nil {
		return ret, err
	}
	for _, snapshot := range snapshots {
		var data T
		if err = snapshot.DataTo(&data); err != nil {
			return ret, err
		}
		ret = append(ret, data)
	}
	return ret, nil
}

func searchBooksAndUpdate(title string) {
	// ... 前処理
	q := f.CollectionGroup("books").Where("title", "==", title)
	books, _ := List[UsersBook](ctx, q)
	for _, book := range books {
		// ... book 更新処理
		_ = Set(ctx, f, &book)
	}
}

メリット

紹介した実装方法でのメリットは以下になるかと思います。

  • ドキュメントへのパス情報を定義した別の型をわざわざ用意しなくても良い
  • クエリで取得したドキュメント配列への更新時の実装がシンプルになる

デメリット

デメリットは下記となります。

  • データがパス情報のみなのかドキュメントの内容を読み込みまで済んだデータなのかが分かりにくい
  • firestore.ClientのAddメソッド(IDを自動発番する)を利用する場合に工夫が必要

Discussion