summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/container.go140
-rw-r--r--service/container_test.go16
-rw-r--r--service/http/config.go8
-rw-r--r--service/injector.go112
-rw-r--r--service/injector_test.go24
-rw-r--r--service/rpc/config_test.go2
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"}