距离上次发版仅两周的时间,Go 后端一站式开发框架 Go-Spring 又发布了新的版本,新版本实现了两个非常重要的特性:动态配置和 Bean 共享。
动态配置
有时候我们想要在不停机的情况下可以修改程序的配置,更改程序的行为,即所谓的“动态配置”。Go-Spring 通过使用专门的数据类型实现了和普通属性一样的使用方式,既支持默认值,也支持类型校验,同时还保证了数据的并发安全,非常简单且强大。
type DynamicConfig struct {
Int dync.Int64 `value:"${int:=3}" validate:"$<6"`
Float dync.Float64 `value:"${float:=1.2}"`
Map dync.Ref `value:"${map:=}"`
Slice dync.Ref `value:"${slice:=}"`
Event dync.Event `value:"${event}"`
}
type DynamicConfigWrapper struct {
Wrapper DynamicConfig `value:"${wrapper}"`
}
func TestDynamic(t *testing.T) {
var cfg *DynamicConfig
wrapper := new(DynamicConfigWrapper)
c := gs.New()
c.Provide(func() *DynamicConfig {
config := new(DynamicConfig)
config.Int.OnValidate(func(v int64) error {
if v < 3 {
return errors.New("should greeter than 3")
}
return nil
})
config.Slice.Init(make([]string, 0))
config.Map.Init(make(map[string]string))
config.Event.OnEvent(func(prop *conf.Properties) error {
fmt.Println("event fired.")
return nil
})
return config
}).Init(func(config *DynamicConfig) {
cfg = config
})
c.Object(wrapper).Init(func(p *DynamicConfigWrapper) {
p.Wrapper.Slice.Init(make([]string, 0))
p.Wrapper.Map.Init(make(map[string]string))
p.Wrapper.Event.OnEvent(func(prop *conf.Properties) error {
fmt.Println("event fired.")
return nil
})
})
err := c.Refresh()
assert.Nil(t, err)
{
b, _ := json.Marshal(cfg)
assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`)
b, _ = json.Marshal(wrapper)
assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}}`)
}
{
p := conf.New()
p.Set("int", 4)
p.Set("float", 2.3)
p.Set("map.a", 1)
p.Set("map.b", 2)
p.Set("slice[0]", 3)
p.Set("slice[1]", 4)
p.Set("wrapper.int", 3)
p.Set("wrapper.float", 1.5)
p.Set("wrapper.map.a", 9)
p.Set("wrapper.map.b", 8)
p.Set("wrapper.slice[0]", 4)
p.Set("wrapper.slice[1]", 6)
c.Properties().Refresh(p)
}
{
b, _ := json.Marshal(cfg)
assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Event":{}}`)
b, _ = json.Marshal(wrapper)
assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.5,"Map":{"a":"9","b":"8"},"Slice":["4","6"],"Event":{}}}`)
}
}
Bean 共享
Java Spring Redis 在首页使用了一个非常特别的特性,可以将一个 Bean 的字段值注入到另一个对象中,看起来就像是 Bean 被共享了。现在 Go-Spring 也能支持这样的使用方式。
type runner struct {
Client *redis.Client `autowire:""`
StrOps *redis.StringOperations `autowire:"RedisClient"`
}
func (r *runner) Run(ctx gs.Context) {
_, err := r.Client.OpsForString().Get(ctx.Context(), "nonexisting")
if !redis.IsErrNil(err) {
panic(errors.New("should be redis.ErrNil"))
}
_, err = r.Client.OpsForString().Set(ctx.Context(), "mykey", "Hello")
util.Panic(err).When(err != nil)
v, err := r.Client.OpsForString().Get(ctx.Context(), "mykey")
util.Panic(err).When(err != nil)
if v != "Hello" {
panic(errors.New("should be \"Hello\""))
}
v, err = r.StrOps.Get(ctx.Context(), "mykey")
util.Panic(err).When(err != nil)
if v != "Hello" {
panic(errors.New("should be \"Hello\""))
}
go gs.ShutDown()
}
func main() {
gs.Object(&runner{}).Export((*gs.AppRunner)(nil))
fmt.Printf("program exited %v\n", gs.Web(false).Run())
}