动机
Firestore本地,基于文档的接口比数据存储模式在使用层次数据时更容易使用。那是您的数据不是基于表/记录的,而是类似的图。但是它不像数据存储模式那样支持自定义填写。但是我已经实施了一项工作。
解决方案
我添加了MarshalMap
和UnmarshalMap
函数,这些功能模仿了MarshalJson
和UnmarshalJson
接口支持的合同。它们可能是接口,但这将需要很多重复的代码,因为所有实现都将与每种类型的这两个函数完全相同。
而不是返回和消费[]byte
,而是在实践中返回和消耗map[string]interface{}
,或者从技术上讲map[string]string
和map[string]interface{}
。
它的工作方式是我将我的自定义结构合并到JSON,然后将它们元使用到map[string]interface{}
,但是由于JSON中的所有字段都是string
类型,因此最终是map[string]string
。
在API中支持类似的东西应该很微不足道。
我知道我的解决方案是一种na -ve解决方案,在计算机上的时间或空间效率不高,但是编写代码时效率很高。
这使我对文档中数据的表示方式以及如何将其转换回JSON的类型安全结构。
使每个值字段成为string
,甚至number
和timestamp
也使我对数据的控制权更大,并在调试时更容易在控制台中阅读。布尔类型是我唯一不需要成为string
的东西,但我通常将它们存储为string
,无论如何都以保持一致性和语义。 yes/no
,on/off
,enabled/disabled
等总是比语义上的true/false
更明确和信息。
这只是其中的第一个版本,当我启动应用程序时,我可以尝试根据需要尝试优化现实世界的数据集和指标。
这是代码
导入以下代码段,我讨厌当人们忽略进口并让您猜测他们实际使用的模块时。
import (
"encoding/json"
"github.com/rs/zerolog/log"
)
这只是json.Marshal()
周围的Must
包装器,因为我想确保所有数据在运行时都正确。是的,我爱Erlang,恨我,如果您愿意,这可以起作用!
func MustMarshalJson(o any) []byte {
bytes, err := json.Marshal(o)
if err != nil {
log.Fatal().Err(err).Msg(err.Error())
return []byte{}
}
return bytes
}
这只是json.Unmarshal()
周围的Must
包装器,因为我想确保所有数据在运行时都正确。是的,我爱Erlang,恨我,如果您愿意,这可以起作用!
func MustUnMarshalJson(bytes []byte, o any) {
err := json.Unmarshal(bytes, o)
if err != nil {
log.Fatal().Err(err).Msg(err.Error())
return
}
return
}
这是一个具有适当json:""
标签或自定义MarshalJson/UnmarshalJson
接口实现的结构,然后将其转换为map[string]interface{}
这是隐式的Must
风格接口,它起作用或失败并阻止了世界。
func MarshallMap[T any](o T) map[string]interface{} {
m := make(map[string]interface{})
MustUnMarshalJson(MustMarshalJson(o), &m)
return m
}
这是一个地图,将其转换为JSON,然后将JSON转换为结构。
这是隐式的Must
风格接口,它起作用或失败并阻止了世界。
func UnmarshallMap[T any](m map[string]interface{}, o T) {
MustUnMarshalJson(MustMarshalJson(m), o)
}
这就是您的使用方式,我包括了样板客户端的创建,我讨厌当人们抛弃使示例不完整的东西时,我猜测您的设置实际上在做什么。
这是文档的写入。
func Create(ctx context.Context, a *Account) error {
client, err := firestore.NewClient(ctx, os.Getenv("GOOGLE_CLOUD_PROJECT"))
if err != nil {
log.Fatal().Err(err).Msg(err.Error())
}
defer func(client *firestore.Client) {
err := client.Close()
if err != nil {
log.Err(err).Msg(err.Error())
}
}(client)
docRef := client.Doc(fmt.Sprintf("account/%s", a.Id))
m := server.MarshallMap[*Account](a)
_, err = docRef.Create(ctx, m)
if err != nil {
return err
}
return nil
}
这是文档中的读取的样子。
func Get(ctx context.Context, id string) (*Account, error) {
client, err := firestore.NewClient(ctx, os.Getenv("GOOGLE_CLOUD_PROJECT"))
if err != nil {
log.Fatal().Err(err).Msg(err.Error())
}
defer func(client *firestore.Client) {
err := client.Close()
if err != nil {
log.Err(err).Msg(err.Error())
}
}(client)
docRef := client.Doc(fmt.Sprintf("account/%s", id))
docSnapshot, err := docRef.Get(ctx)
if err != nil {
return nil, err
}
a := Account{}
server.UnmarshallMap[*Account](docSnapshot.Data(), &a) // << this is the magic part
return &a, nil
}
很棒的事情是,您可以免费元帅/UNMARSHAL。
这是我正在使用的结构定义,这是我正在从事的真实项目中的真实代码。
type Account struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Photo string `json:"photo"`
EmailVerified timestamp.Timestamp `json:"email_verified"`
Created timestamp.Timestamp `json:"created"`
LastUpdated timestamp.Timestamp `json:"last_updated"`
}
和自定义Timestamp
我喜欢用来更好地控制数据的表示。
type Timestamp struct {
t time.Time
}
func (ts Timestamp) String() string {
bytes, _ := ts.MarshalText()
return string(bytes)
}
func (ts Timestamp) MarshalText() (text []byte, err error) {
return []byte(ts.t.UTC().Format(time.RFC3339Nano)), nil
}
func (ts *Timestamp) UnmarshalText(b []byte) error {
t, err := time.Parse(time.RFC3339Nano, string(b))
if err != nil {
return err
}
ts.t = t.UTC()
return nil
}
func (ts Timestamp) MarshalJSON() ([]byte, error) {
return ts.MarshalText()
}
func (ts *Timestamp) UnmarshallJSON(b []byte) error {
return ts.UnmarshalText(b)
}
func (ts Timestamp) MarshalBinary() (data []byte, err error) {
return ts.MarshalText()
}
func (ts *Timestamp) UnmarshalBinary(b []byte) error {
return ts.UnmarshalText(b)
}
我最初将time.Time
作为匿名作品,但我认为我不希望我的Timestamp
隐式转换为time.Time
,因为它在其他模块中混淆了序列化。因此,我给了它一个私人名称t
,并具有可转换/从time.Time
转换/适应的功能,以与其他库的兼容。 Explicit is better than Implicit,我更喜欢python哲学,而不是语言。
func From(t time.Time) Timestamp {
return Timestamp{
t: t.UTC(),
}
}
func To(ts Timestamp) time.Time {
return ts.t
}
您应该始终在UTC
中存储和运输timestamp
数据。
所有计算都应在UTC
值上完成,仅转换为显示的本地时区。