Skip to content

mchmarny/oven

Repository files navigation

oven

Cloud Firestore client helper library

test Go Report Card GitHub go.mod Go version codecov GoDoc

Overview

Cloud Firestore is a Serverless document database. It comes with with multi-regional replication, powerful query engine, and seamless integration into the broader Google Cloud. I found myself use it frequently over last few years. While Firestore exposes both HTTP and RPC APIs to which you can make direct calls, most people rely on one of the client libraries, and the Go library for Firestore is certainly well documented.

Having used Firestore on a few projects though, I did find it wee bit verbose and repetitive in some places. Oven is a wrapper the the standard Firestore client library. It hides some of the complexity (e.g. iterator over resulting documents), and shortens a few of the more verbose, but common, use-cases.

Features

  • Easy Save, Update, Get, Delete
  • Structured criteria Query
  • Extends Firestore client

Install

go get github.com/mchmarny/oven

Usage

The examples folder includes some of the most common use-cases with two fully functional code:

Save

b := Book{
	ID:     "id-123",
	Title:  "The Hitchhiker's Guide to the Galaxy",
	Author: "Douglas Adams",
}

if err := oven.Save(ctx, client, "books", b.ID, &b); err != nil {
	handleErr(err)
}

Get

b, err := oven.Get[Book](ctx, client, "books", "id-123")
if err != nil {
	handleErr(err)
}

Update

Using Title field with firestore:"title" tag

m := map[string]interface{}{
	"title": "Some new title",
}

if err := oven.Update(ctx, client, "books", "id-123", m); err != nil {
	handleErr(err)
}

Delete

if err := oven.Delete(ctx, client, "books", "id-123"); err != nil {
	handleErr(err)
}

Query

q := &oven.Criteria{
	Collection: book.CollectionName,
	Criterions: []*oven.Criterion{
		{
			Path:      "author", // `firestore:"author"`
			Operation: oven.OperationTypeEqual,
			Value:     bookAuthor,
		},
	},
	OrderBy: "published", // `firestore:"published"`
	Desc:    true,
	Limit:   bookCollectionSize,
}

list, err := oven.Query(ctx, client, q)
if err != nil {
	handleErr(err)
}

Iterator

In case you already have the Firestore iterator and want to just avoid the verbose for loop of spooling the documents into a list, oven provides the ToStructs method

// given it as *firestore.DocumentIterator
it := col.Documents(ctx)

// this returns a slice of books ([]*Book)
list, err := oven.ToStructs[Book](it)

There is also support for a handler in case you need to reason over individual item. When handler returns an error, the entire ToStructsWithHandler method will exit with that error.

Only items for which the handler returns true will be appended to the result slice. This allows for filtering the results when such filters are not possible in the Firestore itself.

it := col.Documents(ctx)

return store.ToStructsWithHandler(it, func(item *Person) (bool, error) {
	item.Age = time.Since(item.DOB)
	return true, nil
})

Disclaimer

This is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.