Ok so I have a
REST API in GO
which stores aticket
resource usingfirestore
. For this I use: firestore go clientI want to be able to order my documents by
date created / date updated
, so by following the docs I store these 2 fields as timestamps in the document.I use the tag
serverTimestamp
on these 2 fields. By doing this, the value should be the time at which the firestore server processed the request.The HTTP response of the update operation should have this body:
{
"ticket": {
"id": "af41766e-76ea-43b5-86c1-8ba382edd4dc",
"title": "Ticket updated title",
"price": 9,
"date_created": "2023-01-06 09:07:24",
"date_updated": "2023-01-06 10:08:24"
}
}
So it means after I update the ticket document, besides an updated title or price
I also need to have the updated value fo the date_updated
field.
This is working for the moment but I'm curious if the way I coded this is the way to do it. As you can see in the code samples, I use a transaction to update a ticket. I didn't find a way to retrieve the updated value for the DateUpdated
field, other than reading again the updated ticket.
The domain entity is defined as this:
package tixer
import (
"context"
"time"
"github.com/google/uuid"
)
type (
// TicketID represents a unique identifier for a ticket.
// It's a domain type.
TicketID uuid.UUID
// Ticket represents an individual ticket in the system.
// It's a domain type.
Ticket struct {
ID TicketID
Title string
Price float64
DateCreated time.Time
DateUpdated time.Time
}
)
I'll attach here the communication with firestore from the create and update perspective:
// Storer persists tickets in Firestore.
type Storer struct {
client *firestore.Client
}
func NewStorer(client *firestore.Client) *Storer {
return &Storer{client}
}
func (s *Storer) CreateTicket(ctx context.Context, ticket *tixer.Ticket) error {
writeRes, err := s.client.Collection("tickets").Doc(ticket.ID.String()).Set(ctx, createTicket{
Title: ticket.Title,
Price: ticket.Price,
})
// In this case writeRes.UpdateTime is the time the document was created.
ticket.DateCreated = writeRes.UpdateTime
return err
}
func (s *Storer) UpdateTicket(ctx context.Context, ticket *tixer.Ticket) error {
docRef := s.client.Collection("tickets").Doc(ticket.ID.String())
err := s.client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(docRef)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.ErrTicketNotFound
default:
return err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return err
}
t.ID = doc.Ref.ID
if ticket.Title != "" {
t.Title = ticket.Title
}
if ticket.Price != 0 {
t.Price = ticket.Price
}
return tx.Set(docRef, updateTicket{
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
})
})
if err != nil {
return err
}
updatedTicket, err := s.readTicket(ctx, ticket.ID)
if err != nil {
return err
}
*ticket = updatedTicket
return nil
}
func (s *Storer) readTicket(ctx context.Context, id tixer.TicketID) (tixer.Ticket, error) {
doc, err := s.client.Collection("tickets").Doc(id.String()).Get(ctx)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.Ticket{}, tixer.ErrTicketNotFound
default:
return tixer.Ticket{}, err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return tixer.Ticket{}, err
}
t.ID = doc.Ref.ID
return toDomainTicket(t), nil
}
type (
// persistedTicket represents a stored ticket in Firestore.
persistedTicket struct {
ID string `firestore:"id"`
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate"`
}
// createTicket contains the data needed to create a Ticket in Firestore.
createTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated,serverTimestamp"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
// updateTicket contains the data needed to update a Ticket in Firestore.
updateTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
)
func toDomainTicket(t persistedTicket) tixer.Ticket {
return tixer.Ticket{
ID: tixer.TicketID(uuid.MustParse(t.ID)),
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
DateUpdated: t.DateUpdated,
}
}
CodePudding user response:
If I understand correctly, the DateUpdated
field is a server-side timestamp, which means that its value is determined by the server (as a so-called field transformation) when the value is written to the storage layer. Since a write operation in the Firestore SDK doesn't return the resulting data of that operation, the only way to get that value back into your application is indeed to perform an extra read operation after the write to get it.
The SDK doesn't automatically perform this read is because it is a charged operation, which in many cases is not needed. So by leaving it up to your code to perform that read, you can decide whether to incur this cost or not.