Skip to content

Commit 8c2bc97

Browse files
committed
New feature to save only one field of the configuration
1 parent 30aa6cd commit 8c2bc97

File tree

4 files changed

+400
-114
lines changed

4 files changed

+400
-114
lines changed

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,11 @@ performance isn't the most important issue. Here are some benchmarks (without et
109109
delays):
110110

111111
```
112-
BenchmarkSave 2000000 738 ns/op
113-
BenchmarkLoad 2000000 861 ns/op
114-
BenchmarkWatch 200000 5296 ns/op
115-
BenchmarkVersion 20000000 106 ns/op
112+
BenchmarkSave 2000000 760 ns/op
113+
BenchmarkSaveField 2000000 654 ns/op
114+
BenchmarkLoad 2000000 664 ns/op
115+
BenchmarkWatch 300000 4977 ns/op
116+
BenchmarkVersion 20000000 114 ns/op
116117
```
117118

118119
Fill free to send pull requests to improve the performance or make the code cleaner (I will thank
@@ -161,6 +162,25 @@ func ExampleSave() {
161162
fmt.Printf("%+v\n", a)
162163
}
163164

165+
func ExampleSaveField() {
166+
a := A{
167+
Field1: "value1 changed",
168+
}
169+
170+
client, err := NewClient([]string{"http://127.0.0.1:4001"}, "test", &a)
171+
if err != nil {
172+
fmt.Println(err.Error())
173+
return
174+
}
175+
176+
if err := client.SaveField(&a.Field1); err != nil {
177+
fmt.Println(err.Error())
178+
return
179+
}
180+
181+
fmt.Printf("%+v\n", a)
182+
}
183+
164184
func ExampleLoad() {
165185
var a A
166186

etcetera.go

Lines changed: 112 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -162,118 +162,128 @@ func (c *Client) Save() error {
162162
namespace = "/" + namespace
163163
}
164164

165-
return c.save(c.config, namespace)
165+
return c.saveField(c.config, namespace)
166166
}
167167

168-
func (c *Client) save(config reflect.Value, prefix string) error {
169-
if config.Kind() == reflect.Ptr {
170-
config = config.Elem()
171-
} else if config.Kind() != reflect.Struct {
172-
return ErrInvalidConfig
168+
// SaveField saves a specific field from the configuration structure.
169+
// Works in the same way of Save, but it can be used to save specific parts of the configuration,
170+
// avoiding excessive requests to etcd cluster
171+
func (c *Client) SaveField(field interface{}) error {
172+
path, _, err := c.getInfo(field)
173+
if err != nil {
174+
return err
173175
}
174176

175-
for i := 0; i < config.NumField(); i++ {
176-
field := config.Field(i)
177-
fieldType := config.Type().Field(i)
177+
return c.saveField(reflect.ValueOf(field), path)
178+
}
178179

179-
path := normalizeTag(fieldType.Tag.Get("etcd"))
180-
if len(path) == 0 {
181-
continue
182-
}
183-
path = prefix + "/" + path
180+
func (c *Client) saveField(field reflect.Value, prefix string) error {
181+
if field.Kind() == reflect.Ptr {
182+
field = field.Elem()
183+
}
184184

185-
switch field.Kind() {
186-
case reflect.Struct:
187-
if err := c.save(field, path); err != nil {
188-
return err
185+
switch field.Kind() {
186+
case reflect.Struct:
187+
for i := 0; i < field.NumField(); i++ {
188+
subfield := field.Field(i)
189+
subfieldType := field.Type().Field(i)
190+
191+
path := normalizeTag(subfieldType.Tag.Get("etcd"))
192+
if len(path) == 0 {
193+
continue
189194
}
195+
path = prefix + "/" + path
190196

191-
case reflect.Map:
192-
if _, err := c.etcdClient.CreateDir(path, 0); err != nil && !alreadyExistsError(err) {
197+
if err := c.saveField(subfield, path); err != nil {
193198
return err
194199
}
200+
}
195201

196-
for _, key := range field.MapKeys() {
197-
value := field.MapIndex(key)
198-
tmpPath := path + "/" + key.String()
202+
case reflect.Map:
203+
if _, err := c.etcdClient.CreateDir(prefix, 0); err != nil && !alreadyExistsError(err) {
204+
return err
205+
}
199206

200-
switch value.Kind() {
201-
case reflect.Struct:
202-
if err := c.save(value, tmpPath); err != nil {
203-
return err
204-
}
207+
for _, key := range field.MapKeys() {
208+
value := field.MapIndex(key)
209+
path := prefix + "/" + key.String()
205210

206-
case reflect.String:
207-
if _, err := c.etcdClient.Set(tmpPath, value.String(), 0); err != nil {
208-
return err
209-
}
211+
switch value.Kind() {
212+
case reflect.Struct:
213+
if err := c.saveField(value, path); err != nil {
214+
return err
210215
}
211-
}
212216

213-
case reflect.Slice:
214-
if _, err := c.etcdClient.CreateDir(path, 0); err != nil && !alreadyExistsError(err) {
215-
return err
217+
case reflect.String:
218+
if _, err := c.etcdClient.Set(path, value.String(), 0); err != nil {
219+
return err
220+
}
216221
}
222+
}
217223

218-
for i := 0; i < field.Len(); i++ {
219-
item := field.Index(i)
224+
case reflect.Slice:
225+
if _, err := c.etcdClient.CreateDir(prefix, 0); err != nil && !alreadyExistsError(err) {
226+
return err
227+
}
220228

221-
if item.Kind() == reflect.Struct {
222-
tmpPath := fmt.Sprintf("%s/%d", path, i)
229+
for i := 0; i < field.Len(); i++ {
230+
item := field.Index(i)
223231

224-
if _, err := c.etcdClient.CreateDir(tmpPath, 0); err != nil && !alreadyExistsError(err) {
225-
return err
226-
}
232+
if item.Kind() == reflect.Struct {
233+
path := fmt.Sprintf("%s/%d", prefix, i)
227234

228-
if err := c.save(item, tmpPath); err != nil {
229-
return err
230-
}
235+
if _, err := c.etcdClient.CreateDir(path, 0); err != nil && !alreadyExistsError(err) {
236+
return err
237+
}
231238

232-
} else {
233-
if _, err := c.etcdClient.CreateInOrder(path, item.String(), 0); err != nil {
234-
return err
235-
}
239+
if err := c.saveField(item, path); err != nil {
240+
return err
236241
}
237-
}
238242

239-
case reflect.String:
240-
value := field.Interface().(string)
241-
if _, err := c.etcdClient.Set(path, value, 0); err != nil {
242-
return err
243+
} else {
244+
if _, err := c.etcdClient.CreateInOrder(prefix, item.String(), 0); err != nil {
245+
return err
246+
}
243247
}
248+
}
244249

245-
case reflect.Int:
246-
value := field.Interface().(int)
247-
if _, err := c.etcdClient.Set(path, strconv.FormatInt(int64(value), 10), 0); err != nil {
248-
return err
249-
}
250+
case reflect.String:
251+
value := field.Interface().(string)
252+
if _, err := c.etcdClient.Set(prefix, value, 0); err != nil {
253+
return err
254+
}
250255

251-
case reflect.Int64:
252-
value := field.Interface().(int64)
253-
if _, err := c.etcdClient.Set(path, strconv.FormatInt(value, 10), 0); err != nil {
254-
return err
255-
}
256+
case reflect.Int:
257+
value := field.Interface().(int)
258+
if _, err := c.etcdClient.Set(prefix, strconv.FormatInt(int64(value), 10), 0); err != nil {
259+
return err
260+
}
256261

257-
case reflect.Bool:
258-
value := field.Interface().(bool)
262+
case reflect.Int64:
263+
value := field.Interface().(int64)
264+
if _, err := c.etcdClient.Set(prefix, strconv.FormatInt(value, 10), 0); err != nil {
265+
return err
266+
}
259267

260-
var valueStr string
261-
if value {
262-
valueStr = "true"
263-
} else {
264-
valueStr = "false"
265-
}
268+
case reflect.Bool:
269+
value := field.Interface().(bool)
266270

267-
if _, err := c.etcdClient.Set(path, valueStr, 0); err != nil {
268-
return err
269-
}
271+
var valueStr string
272+
if value {
273+
valueStr = "true"
274+
} else {
275+
valueStr = "false"
270276
}
271277

272-
c.info[path] = info{
273-
field: field,
278+
if _, err := c.etcdClient.Set(prefix, valueStr, 0); err != nil {
279+
return err
274280
}
275281
}
276282

283+
c.info[prefix] = info{
284+
field: field,
285+
}
286+
277287
return nil
278288
}
279289

@@ -335,32 +345,14 @@ func (c *Client) load(config reflect.Value, prefix string) error {
335345
// could have a strange behavior since there are two go routines listening on it (go-etcd and
336346
// etcetera watch functions)
337347
func (c *Client) Watch(field interface{}, callback func()) (chan<- bool, error) {
348+
path, _, err := c.getInfo(field)
349+
if err != nil {
350+
return nil, err
351+
}
352+
338353
fieldValue := reflect.ValueOf(field)
339354
if fieldValue.Kind() == reflect.Ptr {
340355
fieldValue = fieldValue.Elem()
341-
342-
} else if !fieldValue.CanAddr() {
343-
return nil, ErrFieldNotAddr
344-
}
345-
346-
var path string
347-
var info info
348-
349-
found := false
350-
for path, info = range c.info {
351-
// Match the pointer, type and name to avoid problems for struct and first field that have the
352-
// same memory address
353-
if info.field.Addr().Pointer() == fieldValue.Addr().Pointer() &&
354-
info.field.Type().Name() == fieldValue.Type().Name() &&
355-
info.field.Kind() == fieldValue.Kind() {
356-
357-
found = true
358-
break
359-
}
360-
}
361-
362-
if !found {
363-
return nil, ErrFieldNotMapped
364356
}
365357

366358
stop := make(chan bool)
@@ -543,26 +535,41 @@ func (c *Client) fillField(field reflect.Value, node *etcd.Node, prefix string)
543535
// It does not query etcd for the latest version. When the field was not retrieved from etcd yet,
544536
// the version 0 is returned
545537
func (c *Client) Version(field interface{}) (uint64, error) {
538+
_, info, err := c.getInfo(field)
539+
if err != nil {
540+
return 0, err
541+
}
542+
return info.version, nil
543+
}
544+
545+
func (c *Client) getInfo(field interface{}) (path string, info info, err error) {
546546
fieldValue := reflect.ValueOf(field)
547547
if fieldValue.Kind() == reflect.Ptr {
548548
fieldValue = fieldValue.Elem()
549549

550550
} else if !fieldValue.CanAddr() {
551-
return 0, ErrFieldNotAddr
551+
err = ErrFieldNotAddr
552+
return
552553
}
553554

554-
for _, info := range c.info {
555+
found := false
556+
for path, info = range c.info {
555557
// Match the pointer, type and name to avoid problems for struct and first field that have the
556558
// same memory address
557559
if info.field.Addr().Pointer() == fieldValue.Addr().Pointer() &&
558560
info.field.Type().Name() == fieldValue.Type().Name() &&
559561
info.field.Kind() == fieldValue.Kind() {
560562

561-
return info.version, nil
563+
found = true
564+
break
562565
}
563566
}
564567

565-
return 0, ErrFieldNotMapped
568+
if !found {
569+
err = ErrFieldNotMapped
570+
}
571+
572+
return
566573
}
567574

568575
// normalizeTag removes the slash from the beggining or end of the tag name and replace the other

0 commit comments

Comments
 (0)