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/container.go | |
parent | f688229fe93016064ad67b381e774881d991b8ab (diff) |
minor CS
Diffstat (limited to 'service/container.go')
-rw-r--r-- | service/container.go | 140 |
1 files changed, 123 insertions, 17 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 +} |