Do I need extra round trip to firestore for reading Created & Updated timestamps fields?

huangapple go评论83阅读模式
英文:

Do I need extra round trip to firestore for reading Created & Updated timestamps fields?

问题

好的,以下是翻译的内容:

  1. 好的,我有一个使用firestore存储ticket资源的GO REST API。为此,我使用了:firestore go client

  2. 我希望能够按照创建日期/更新日期对文档进行排序,所以按照文档的说明,我将这两个字段存储为文档中的时间戳。

  3. 我在这两个字段上使用了serverTimestamp标签。通过这样做,该值应该是firestore服务器处理请求的时间。

  4. 更新操作的HTTP响应应该具有以下主体:

{
  "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"
  }
}

这意味着在更新票据文档之后,除了更新的标题或价格之外,我还需要更新的date_updated字段的值。

目前这种方式是有效的,但我想知道我编写的方式是否正确。正如您在代码示例中看到的,我使用事务来更新票据。我没有找到一种方法来检索DateUpdated字段的更新值,除非重新读取更新后的票据。

该域实体定义如下:

package tixer

import (
	"context"
	"time"

	"github.com/google/uuid"
)

type (
	// TicketID表示票据的唯一标识符。
	// 这是一个领域类型。
	TicketID uuid.UUID

	// Ticket表示系统中的单个票据。
	// 这是一个领域类型。
	Ticket struct {
		ID          TicketID
		Title       string
		Price       float64
		DateCreated time.Time
		DateUpdated time.Time
	}
)

我将在这里附上与创建和更新方面的firestore通信:

// Storer在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,
	})

	// 在这种情况下,writeRes.UpdateTime是文档创建的时间。
	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表示在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包含在Firestore中创建票据所需的数据。
	createTicket struct {
		Title       string    `firestore:"title"`
		Price       float64   `firestore:"price"`
		DateCreated time.Time `firestore:"dateCreated,serverTimestamp"`
		DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
	}

	// updateTicket包含在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,
	}
}

英文:
  1. Ok so I have a REST API in GO which stores a ticket resource using firestore. For this I use: firestore go client

  2. I 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.

  3. 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.

  4. 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,
	}
}

答案1

得分: 1

如果我理解正确,DateUpdated字段是一个服务器端时间戳,这意味着它的值是由服务器确定的(作为所谓的字段转换),当该值被写入存储层时。由于Firestore SDK中的写操作不会返回该操作的结果数据,因此唯一的方法是在写入后执行额外的读操作来获取该值返回到应用程序中。

SDK不会自动执行这个读操作,因为它是一项收费操作,在许多情况下是不需要的。因此,通过让您的代码执行该读操作,您可以决定是否承担这个成本。

英文:

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.

huangapple
  • 本文由 发表于 2023年1月6日 19:22:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/75030307.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定