diff options
author | Wolfy-J <[email protected]> | 2018-07-08 21:35:11 -0700 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2018-07-08 21:35:11 -0700 |
commit | 2f2b3137d243a75a98cf2a0a3ab32e7fb407e48d (patch) | |
tree | 4ba6be5a20c4bb58aa28d73b4cd3dc17371c5de3 /service | |
parent | f688229fe93016064ad67b381e774881d991b8ab (diff) |
minor CS
Diffstat (limited to 'service')
-rw-r--r-- | service/container.go | 140 | ||||
-rw-r--r-- | service/container_test.go | 16 | ||||
-rw-r--r-- | service/http/config.go | 8 | ||||
-rw-r--r-- | service/injector.go | 112 | ||||
-rw-r--r-- | service/injector_test.go | 24 | ||||
-rw-r--r-- | service/rpc/config_test.go | 2 |
6 files changed, 143 insertions, 159 deletions
diff --git a/service/container.go b/service/container.go index 02ebbf26..7eff4551 100644 --- a/service/container.go +++ b/service/container.go @@ -5,8 +5,27 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "sync" + "reflect" ) +var noConfig = fmt.Errorf("no config has been provided") + +// InitMethod contains name of the method to be automatically invoked while service initialization. Must return +// (bool, error). Container can be requested as well. Config can be requested in a form +// of service.Config or pointer to service specific config struct (automatically unmarshalled), config argument must +// implement service.HydrateConfig. +const InitMethod = "Init" + +// Service can serve. Service can provide Init method which must return (bool, error) signature and might accept +// other services and/or configs as dependency. +type Service interface { + // Serve serves. + Serve() error + + // Stop stops the service. + Stop() +} + // Container controls all internal RR services and provides plugin based system. type Container interface { // Register add new service to the container under given name. @@ -29,18 +48,6 @@ type Container interface { Stop() } -// Service can serve. Service can provide Init method which must return (bool, error) signature and might accept -// other services and/or configs as dependency. Container can be requested as well. Config can be requested in a form -// of service.Config or pointer to service specific config struct (automatically unmarshalled), config argument must -// implement service.HydrateConfig. -type Service interface { - // Serve serves. - Serve() error - - // Stop stops the service. - Stop() -} - // Config provides ability to slice configuration sections and unmarshal configuration data into // given structure. type Config interface { @@ -122,12 +129,11 @@ func (c *container) Init(cfg Config) error { return fmt.Errorf("service [%s] has already been configured", e.name) } - // inject service dependencies (todo: move to container) - if ok, err := initService(e.svc, cfg.Get(e.name), c); err != nil { + // inject service dependencies + if ok, err := c.initService(e.svc, cfg.Get(e.name)); err != nil { + // soft error (skipping) if err == noConfig { c.log.Warningf("[%s]: no config has been provided", e.name) - - // unable to meet dependency requirements, skippingF continue } @@ -143,7 +149,6 @@ func (c *container) Init(cfg Config) error { return nil } -//todo: refactor ???? // Serve all configured services. Non blocking. func (c *container) Serve() error { var ( @@ -193,6 +198,7 @@ func (c *container) Serve() error { // Stop sends stop command to all running services. func (c *container) Stop() { c.log.Debugf("received stop command") + for _, e := range c.services { if e.hasStatus(StatusServing) { e.svc.(Service).Stop() @@ -202,3 +208,103 @@ func (c *container) Stop() { } } } + +// calls Init method with automatically resolved arguments. +func (c *container) initService(s interface{}, segment Config) (bool, error) { + r := reflect.TypeOf(s) + + m, ok := r.MethodByName("Init") + if !ok { + // no Init method is presented, assuming service does not need initialization. + return false, nil + } + + if err := c.verifySignature(m); err != nil { + return false, err + } + + // hydrating + values, err := c.resolveValues(s, m, segment) + if err != nil { + return false, err + } + + // initiating service + out := m.Func.Call(values) + + if out[1].IsNil() { + return out[0].Bool(), nil + } + + return out[0].Bool(), out[1].Interface().(error) +} + +// resolveValues returns slice of call arguments for service Init method. +func (c *container) resolveValues(s interface{}, m reflect.Method, cfg Config) (values []reflect.Value, err error) { + for i := 0; i < m.Type.NumIn(); i++ { + v := m.Type.In(i) + + switch { + case v.ConvertibleTo(reflect.ValueOf(s).Type()): // service itself + values = append(values, reflect.ValueOf(s)) + + case v.Implements(reflect.TypeOf((*Container)(nil)).Elem()): // container + values = append(values, reflect.ValueOf(c)) + + case v.Implements(reflect.TypeOf((*HydrateConfig)(nil)).Elem()): // injectable config + if cfg == nil { + return nil, noConfig + } + + sc := reflect.New(v.Elem()) + if err := sc.Interface().(HydrateConfig).Hydrate(cfg); err != nil { + return nil, err + } + + values = append(values, sc) + + case v.Implements(reflect.TypeOf((*Config)(nil)).Elem()): // generic config section + if cfg == nil { + return nil, noConfig + } + + values = append(values, reflect.ValueOf(cfg)) + + default: // dependency on other service (resolution to nil if service can't be found) + found := false + for _, e := range c.services { + if !e.hasStatus(StatusOK) || !v.ConvertibleTo(reflect.ValueOf(e.svc).Type()) { + continue + } + + found = true + values = append(values, reflect.ValueOf(e.svc)) + break + } + + if !found { + // placeholder (make sure to check inside the method) + values = append(values, reflect.New(v).Elem()) + } + } + } + + return +} + +// verifySignature checks if Init method has valid signature +func (c *container) verifySignature(m reflect.Method) error { + if m.Type.NumOut() != 2 { + return fmt.Errorf("method Init must have exact 2 return values") + } + + if m.Type.Out(0).Kind() != reflect.Bool { + return fmt.Errorf("first return value of Init method must be bool type") + } + + if !m.Type.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { + return fmt.Errorf("second return value of Init method value must be error type") + } + + return nil +} diff --git a/service/container_test.go b/service/container_test.go index bf95cbd4..d2ca1f03 100644 --- a/service/container_test.go +++ b/service/container_test.go @@ -325,3 +325,19 @@ func TestContainer_ServeErrorMultiple(t *testing.T) { assert.IsType(t, &testService{}, s) assert.Equal(t, StatusStopped, st) } + +//func TestContainer_Init(t *testing.T) { +// logger, hook := test.NewNullLogger() +// logger.SetLevel(logrus.DebugLevel) +// +// svc := &testService{ok: true} +// +// c := NewContainer(logger) +// c.Register("test", svc) +// c.Register("test2", struct{}{}) +// +// assert.Equal(t, 2, len(hook.Entries)) +// +// assert.NoError(t, c.Serve()) +// c.Stop() +//} diff --git a/service/http/config.go b/service/http/config.go index d50f59f0..20a247fb 100644 --- a/service/http/config.go +++ b/service/http/config.go @@ -32,14 +32,14 @@ func (c *Config) Hydrate(cfg service.Config) error { return err } - if c.Workers.Relay == "" { - c.Workers.Relay = "pipes" - } - if err := c.Valid(); err != nil { return err } + if c.Workers.Relay == "" { + c.Workers.Relay = "pipes" + } + if c.Workers.RelayTimeout < time.Microsecond { c.Workers.RelayTimeout = time.Second * time.Duration(c.Workers.RelayTimeout.Nanoseconds()) } diff --git a/service/injector.go b/service/injector.go deleted file mode 100644 index 3c41240a..00000000 --- a/service/injector.go +++ /dev/null @@ -1,112 +0,0 @@ -package service - -import ( - "fmt" - "reflect" -) - -const initMethod = "Init" - -var noConfig = fmt.Errorf("no config has been provided") - -// calls Init method with automatically resolved arguments. -func initService(s interface{}, cfg Config, c *container) (bool, error) { - r := reflect.TypeOf(s) - - m, ok := r.MethodByName(initMethod) - if !ok { - // no Init method is presented, assuming service does not need - // initialization. - return false, nil - } - - if err := verifySignature(m); err != nil { - return false, err - } - - // hydrating - values, err := injectValues(m, s, cfg, c) - if err != nil { - return false, err - } - - // initiating service - out := m.Func.Call(values) - - if out[1].IsNil() { - return out[0].Bool(), nil - } - - return out[0].Bool(), out[1].Interface().(error) -} - -// injectValues returns slice of call arguments for service Init method. -func injectValues(m reflect.Method, s interface{}, cfg Config, c *container) (values []reflect.Value, err error) { - for i := 0; i < m.Type.NumIn(); i++ { - v := m.Type.In(i) - - switch { - case v.ConvertibleTo(reflect.ValueOf(s).Type()): // service itself - values = append(values, reflect.ValueOf(s)) - - case v.Implements(reflect.TypeOf((*HydrateConfig)(nil)).Elem()): // automatically configured config - if cfg == nil { - // todo: generic value - return nil, noConfig - } - - sc := reflect.New(v.Elem()) - if err := sc.Interface().(HydrateConfig).Hydrate(cfg); err != nil { - return nil, err - } - - values = append(values, sc) - - case v.Implements(reflect.TypeOf((*Config)(nil)).Elem()): // config section - if cfg == nil { - // todo: generic value - return nil, noConfig - } - values = append(values, reflect.ValueOf(cfg)) - - case v.Implements(reflect.TypeOf((*Container)(nil)).Elem()): // container - values = append(values, reflect.ValueOf(c)) - - default: - found := false - - // looking for the service candidate - for _, e := range c.services { - if v.ConvertibleTo(reflect.ValueOf(e.svc).Type()) && e.hasStatus(StatusOK) { - found = true - values = append(values, reflect.ValueOf(e.svc)) - break - } - } - - if !found { - // placeholder (make sure to check inside the method) - values = append(values, reflect.New(v).Elem()) - } - } - } - - return -} - -// verifySignature checks if Init method has valid signature -func verifySignature(m reflect.Method) error { - if m.Type.NumOut() != 2 { - return fmt.Errorf("method Init must have exact 2 return values") - } - - if m.Type.Out(0).Kind() != reflect.Bool { - return fmt.Errorf("first return value of Init method must be bool type") - } - - if !m.Type.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { - return fmt.Errorf("second return value of Init method value must be error type") - } - - return nil -} diff --git a/service/injector_test.go b/service/injector_test.go deleted file mode 100644 index facc3f74..00000000 --- a/service/injector_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package service - -import ( - "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/test" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestContainer_Init(t *testing.T) { - logger, hook := test.NewNullLogger() - logger.SetLevel(logrus.DebugLevel) - - svc := &testService{ok: true} - - c := NewContainer(logger) - c.Register("test", svc) - c.Register("test2", struct{}{}) - - assert.Equal(t, 2, len(hook.Entries)) - - assert.NoError(t, c.Serve()) - c.Stop() -} diff --git a/service/rpc/config_test.go b/service/rpc/config_test.go index 87a89a2b..8642e9ab 100644 --- a/service/rpc/config_test.go +++ b/service/rpc/config_test.go @@ -6,8 +6,6 @@ import ( "testing" ) -// todo: test hydrate - func TestConfig_Listener(t *testing.T) { cfg := &Config{Listen: "tcp://:18001"} |