Documentation ¶
Overview ¶
Package firestore implements helper functions and utilities to make working with package "cloud.google.com/go/firestore" easier.
Index ¶
- func CreateDocument(ctx context.Context, col *firestore.CollectionRef, id string, doc interface{}) error
- func DeleteDocument(ctx context.Context, col *firestore.CollectionRef, id string) error
- func GetDocumentById(ctx context.Context, col *firestore.CollectionRef, id string, ...) error
- func GetDocumentSnapshotById(ctx context.Context, col *firestore.CollectionRef, id string) (*firestore.DocumentSnapshot, error)
- func GetDocumentsForQuery(ctx context.Context, query firestore.Query) ([]*firestore.DocumentSnapshot, error)
- func NewFirestoreError(inner error, code errors.ErrorCode) errors.Error
- func NewFirestoreErrorInternal(inner error) errors.Error
- func ParseFirestoreError(err error) errors.Error
- func SetDocument(ctx context.Context, col *firestore.CollectionRef, id string, data interface{}, ...) error
- func UnmarshalDocSnapshot(snap *firestore.DocumentSnapshot, result interface{}) error
- func UpdateDocument(ctx context.Context, col *firestore.CollectionRef, id string, ...) error
- func VerifyTransactionExpectations(t *firestore.Transaction, te TransactionExpectations) error
- type TransactionExpectation
- type TransactionExpectations
- func (tes TransactionExpectations) Add(te TransactionExpectation)
- func (tes TransactionExpectations) Combine(other TransactionExpectations) TransactionExpectations
- func (tes TransactionExpectations) DocRefs() []*firestore.DocumentRef
- func (tes TransactionExpectations) Get(docRef *firestore.DocumentRef) (TransactionExpectation, bool)
- func (tes TransactionExpectations) Remove(docRef *firestore.DocumentRef)
- func (tes TransactionExpectations) Verify(snaps []*firestore.DocumentSnapshot) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CreateDocument ¶
func CreateDocument(ctx context.Context, col *firestore.CollectionRef, id string, doc interface{}) error
Create a new document with the given id in the given collection.
func DeleteDocument ¶
func GetDocumentById ¶
func GetDocumentById(ctx context.Context, col *firestore.CollectionRef, id string, result interface{}) error
Gets and unmarshals the document with the given id into result.
func GetDocumentSnapshotById ¶
func GetDocumentSnapshotById(ctx context.Context, col *firestore.CollectionRef, id string) (*firestore.DocumentSnapshot, error)
func GetDocumentsForQuery ¶
func ParseFirestoreError ¶
Parses the given firestore error and returns an instance of Error from package "github.com/dkinzler/kit/errors" with an appropriate error code set.
func SetDocument ¶
func SetDocument(ctx context.Context, col *firestore.CollectionRef, id string, data interface{}, opts ...firestore.SetOption) error
Without merge options, can provide most data types (struct, map, slice, ...), document will be completely overridden. With "MergeAll" option, can only use map as data argument. With "Merge" option, only the provided fields will be overridden, can use structs as data argument.
func UnmarshalDocSnapshot ¶
func UnmarshalDocSnapshot(snap *firestore.DocumentSnapshot, result interface{}) error
Unmarshal the given snapshot into result, which should usually be a pointer to a struct or map.
func UpdateDocument ¶
func VerifyTransactionExpectations ¶
func VerifyTransactionExpectations(t *firestore.Transaction, te TransactionExpectations) error
Verifies the transaction expectations in the given firestore transaction.
Types ¶
type TransactionExpectation ¶
type TransactionExpectation struct { DocRef *firestore.DocumentRef Exists bool UpdateTime time.Time }
TransactionExpectation represents the state of a document, i.e. whether or not it exists and if it exists the last time it was updated/modified. Can be used to implement safe optimistic transactions.
func TransactionExpectationFromSnapshot ¶
func TransactionExpectationFromSnapshot(snap *firestore.DocumentSnapshot) TransactionExpectation
func (TransactionExpectation) IsSatisfied ¶
func (te TransactionExpectation) IsSatisfied(snap *firestore.DocumentSnapshot) error
Returns nil if the given document snapshot satisfies the transaction expectation, i.e. they both refer to the same document and existence as well as latest update times are equal.
type TransactionExpectations ¶
type TransactionExpectations map[string]TransactionExpectation
A set of transaction expectations, that can be used to implement optimistic concurrency/transactions. Functions that read data from firestore can return TransactionExpectations values, multiple of them can be combined. If we then try to update the data and want to make sure that it hasn't changed since we last read it, we can use a transaction that first gets the documents and then compares them to the TransactionExpectations value using the VerifyTransactionExpectations() function.
Optmistic concurrency works well if the probability of concurrent modifications is low. It has the advantage that it is easy to create code that guarantees consistency while not leaking any implementation details of the data store layer into business logic code.
Example ¶
package main import ( "context" "cloud.google.com/go/firestore" "github.com/dkinzler/kit/errors" ) // An example of how to integrate optimistic concurrency/transactions with application services and data stores based on firestore. type Folder struct { FolderId string Name string // Ids of the notes contained in this folder Notes []string } func (f *Folder) ContainsNote(noteId string) bool { for _, n := range f.Notes { if n == noteId { return true } } return false } func (f *Folder) AddNote(noteId string) { f.Notes = append(f.Notes, noteId) } type Note struct { NoteId string Text string } // Any type implementing this interface can be used as the data store for the note taking application. // To implement optimistic concurrency/transactions, methods that read data return a transaction expectation (interface type TE). // Methods that write/update data take a transaction expectation and must guarantee that the write is only performed // if the transaction expectations are still satisfied (i.e. the data they represent was not modified since the time the transaction expectation was created). type NoteDatastore interface { Folder(ctx context.Context, folderId string) (Folder, TE, error) Note(ctx context.Context, id string) (Note, TE, error) UpdateFolder(ctx context.Context, folderId string, f Folder, te TE) error } // Transaction expectations, usually a timestamp representing the last time some piece of data was modified. type TE interface { Combine(other TE) TE } func main() { // Imagine this is part of a method in a note taking application, where // we want to add a note to a folder. A folder can contain multiple notes. // To do this, we need to load the note and the folder from the data store. // Then we update the folder by adding the note to it and then persist // the changes by updating the folder in the data store. // We need to make sure that the data stays consistent, e.g. if a concurrent operation deleted the folder or the note, // the update operation on the data store should fail. // To this end we combine the transaction expectations for the folder and note // and pass them along to the UpdateFolder method of the data store. // The data store implementation can then make sure to only perform the update if the note and folder were not modified. // // firestoreNoteDatastore is an example implementation of NoteDatastore using firestore. It demonstrates how to use // transaction expectations to implement optimistic concurrency. ds := NewFirestoreNoteDatastore() folder, te1, _ := ds.Folder(context.Background(), "folder1234") note, te2, _ := ds.Note(context.Background(), "note1") if !folder.ContainsNote(note.NoteId) { folder.AddNote(note.NoteId) } te := te1.Combine(te2) ds.UpdateFolder(context.Background(), folder.FolderId, folder, te) } type firestoreNoteDatastore struct { client *firestore.Client folderCollection *firestore.CollectionRef noteCollection *firestore.CollectionRef } func NewFirestoreNoteDatastore() *firestoreNoteDatastore { // In an actual application we would have to provide a firestore client and initialize the collections. return &firestoreNoteDatastore{} } type firestoreTE TransactionExpectations func (fte firestoreTE) Combine(other TE) TE { if ote, ok := other.(firestoreTE); ok { return fte.Combine(ote) } return fte } func (fds *firestoreNoteDatastore) Folder(ctx context.Context, folderId string) (Folder, TE, error) { var folder Folder te, err := GetDocumentByIdWithTE(ctx, fds.folderCollection, folderId, &folder) if err != nil { return Folder{}, nil, err } return folder, firestoreTE(te), nil } func (fds *firestoreNoteDatastore) Note(ctx context.Context, noteId string) (Note, TE, error) { var note Note te, err := GetDocumentByIdWithTE(ctx, fds.folderCollection, noteId, ¬e) if err != nil { return Note{}, nil, err } return note, firestoreTE(te), nil } func (fds *firestoreNoteDatastore) UpdateFolder(ctx context.Context, folderId string, f Folder, te TE) error { err := fds.client.RunTransaction(ctx, func(ctx context.Context, t *firestore.Transaction) error { if te != nil { // make sure the transaction expectation passed to the method has the correct type tmp, ok := te.(firestoreTE) if !ok { return NewFirestoreError(nil, errors.InvalidArgument) } fte := TransactionExpectations(tmp) // verify the transaction expectations, i.e. none of the documents have been modified if err := VerifyTransactionExpectations(t, fte); err != nil { return err } } err := t.Set(fds.folderCollection.Doc(folderId), f) if err != nil { return err } return nil }, firestore.MaxAttempts(1)) return err }
Output:
func GetDocumentByIdWithTE ¶
func GetDocumentByIdWithTE(ctx context.Context, col *firestore.CollectionRef, id string, result interface{}) (TransactionExpectations, error)
Gets and unmarshals the document with the given id into result. Also returns a TransactionExpectations value that represents the last time the document was modified. Can be used to implement optimistic transactions.
func (TransactionExpectations) Add ¶
func (tes TransactionExpectations) Add(te TransactionExpectation)
func (TransactionExpectations) Combine ¶
func (tes TransactionExpectations) Combine(other TransactionExpectations) TransactionExpectations
Combine two sets of transaction expectations. If both sets contain an expectation for the same document, the expectation with the more recent update time will be used.
func (TransactionExpectations) DocRefs ¶
func (tes TransactionExpectations) DocRefs() []*firestore.DocumentRef
func (TransactionExpectations) Get ¶
func (tes TransactionExpectations) Get(docRef *firestore.DocumentRef) (TransactionExpectation, bool)
func (TransactionExpectations) Remove ¶
func (tes TransactionExpectations) Remove(docRef *firestore.DocumentRef)
func (TransactionExpectations) Verify ¶
func (tes TransactionExpectations) Verify(snaps []*firestore.DocumentSnapshot) error
Verifies that the given document snapshots are compatible/consistent with the set of transaction expectations, i.e. none of the documents were changed since the time the transaction expectations were created. Formally for each document snapshot it must be true that there is a transaction expectation for the same document and the document existence and latest update time of the snaphost and transaction expectation are equal.
Note that there must be a transaction expectation for every snapshot, but not the other way around.