Struct embedding in Go

用户头像
Interstate5
关注
发布于: 2020 年 07 月 09 日

最近组里在AWS开发一个云原生服务,选择的语言是Go。磕磕碰碰一年有余,却还没有正式发布。其实当初如果选择Java来开发,感觉效率能提高一倍,因为大部分组员都是Java开发出生,但是用Go的话,大家都是新手,得从头学期。既然如此,为何用Go?好问题。我算是最晚加入这个组的程序员,传言说是因为tech lead喜欢一些新玩意,觉得用Go来开发服务很酷。



闲话少说。在用Go开发的过程中,碰到了各种各样的问题。其中一个有关timestamp的问题困扰了我许久,最终通过各种hack,外加与其他组扯皮,终于把问题解决了。



问题是这样的。后台服务在API Gateway的model里用Number类定义了timestamp,然后在DynamoDB里也是用Number存储的timestamp,比如1594278495.789。后台服务某个API的request里定义了若干类型为*time.Time的fields,比如



type ListDummyRequest struct {
...
CreatedTimeAfter *time.Time
CreatedTimeBefore *time.Time
}



碰到的第一个问题是,后台服务从API Gateway收到的request里timestamp都是1594278495.789这类形式的,但time.Time的JSON marshal和unmarshal只接受RFC 3339格式。于是ListDummy API报错。



碰到的第二个问题是,time.Time在DynamoDB里默认存为RFC 3339格式的string,如果要存为number,只能是Unix time,不带millisecond。而我们的timestamp要求带millisecond,second的精度不够。



两个问题其实都好解决,如果可以用string类型来定义API Gateway和DynamoDB的timestamp,这样API Gateway和后台服务(包括DynamoDB)的交互通畅无阻。但因为某些未知原因,tech lead不喜欢这样,于是只能另辟蹊径。



尝试过直接重定义time.Time的JSON和DynamoDB Attribute的marshal和unmarshal方法,但是Go不支持。最后想出的方案是用自定义的Time,里面定义了想要的JSON和DynamoDB Attribute的marshal和unmarshal方法。

package custom
import (
"time"
"github.com/aws/aws-sdk-go/service/dynamodb"
...
)
type Time time.Time
// Marshal Time to string, e.g., "1594278495.789".
func (t Time) MarshalJSON() ([]byte, error) {
// code here
}
// Unmarshal string (e.g., 1594278495.789) to Time.
func (t *Time) UnmarshalJSON(bytes []byte) error {
// code here
}
// Marshal Time to DynamoDB AttributeValue (e.g., "1594278495.789").
func (t Time) MarshalDynamoDBAttributeValue(attribute *dynamodb.AttributeValue) error {
// code here
}
// Unmarshal DynamoDB AttributeValue (e.g., "1594278495.789") to Time.
func (t *Time) UnmarshalDynamoDBAttributeValue(attribute *dynamodb.AttributeValue) error {
// code here
}
// Use time.Time.String() for string rendering
func (t Time) String() string {
return time.Time(t).String()
}



对ListDummy API来说,因为我们的API client是自动生成的,没法更改request和response里timestamp的类别,所以用到了type embedding(感兴趣的请移步Effective Go探个究竟)。首先定义一个ListDummyRequest的wrapper,wrapper里定义了和ListDummyRequest同名的timestamp fields,但它们的类型是自定义的Time。

// Wrapper of ListDummyRequest.
type ListDummyRequestWrapper struct {
CreatedTimeAfter *custom.Time
CreatedTimeBefore *custom.Time
}

然后在API Gateway和API之间的middleware层用ListDummyRequestWrapper来接受API Gateway发来的request。request的marshal和unmarshal会使用customer.Time里定义的方法。然后在ListDummy API里做一次ListDummyRequestWrapper到ListDummyRequest的转换,但是得设置ListDummyRequest里的timestamp,因为它们被wrapper里的那些同名的fields覆盖了。



对DynamoDB来说,只要在对应的DAO里,用custom.Time定义timestamps,timestamp的存储就会按自定义的方法去marshal和unmarshal。

发布于: 2020 年 07 月 09 日 阅读数: 51
用户头像

Interstate5

关注

既然选择了远方 ,便只顾风雨兼程。 2014.08.23 加入

计算机博士|软件工程师|思考者|实践者

评论

发布
暂无评论
Struct embedding in Go