I'm trying to understand how to build a service container to go. Though it's not a recommended process still I want to learn something from it.
But after structuring code like this image below.
I'm getting an error message like this below.
Source code:
package unit
import (
"testing"
"github.com/stretchr/testify/assert"
framework "../../framework"
)
type ContainerFunc func(container Container) interface{}
type MyService struct {
Name string
}
type Container framework.Container
func TestContainerSingleton(t *testing.T) {
c := framework.New()
assert.False(t, c.Has("test.service.name"))
assert.Equal(t, []string{}, c.GetKeys())
c.Set("my.service", func(c Container) interface{} {
return &MyService{}
})
}
container.go
package framework
import (
"fmt"
"log"
"reflect"
"sync"
)
// Container is for containing any services
type Container interface {
Set(name string, f ContainerFunc)
Has(name string) bool
Get(name string) interface{}
GetKeys() []string
Fill(name string, ds interface{})
Extend(name string, f ExtenderFunc)
}
// ContainerFunc func will generate interfaces based on need
type ContainerFunc func(container Container) interface{}
// ExtenderFunc means interface
type ExtenderFunc interface{}
type container struct {
values map[string]ContainerFunc
extends map[string][]reflect.Value
services map[string]interface{}
mtx *sync.RWMutex
}
//New method initialize a new Container and returns it.
func New() Container {
return &container{
services: make(map[string]interface{}),
values: make(map[string]ContainerFunc),
extends: make(map[string][]reflect.Value),
mtx: &sync.RWMutex{},
}
}
/*
In Set method storing the container
*/
func (c *container) Set(name string, f ContainerFunc) {
c.mtx.Lock()
defer c.mtx.Unlock()
//Checking if the service is in the format, if service is there throw a panic
if _, ok := c.services[name]; ok {
log.Panic("Can not overwrite initialized service")
}
c.values[name] = f
fmt.Printf("%s service has been registered", name)
}
/*
Has Checks if the name exists in map and return boolean value
it first locks the c.values for reading then checks and at last using
defer it release the lock
*/
func (c *container) Has(name string) bool {
//applying read lock on value map
c.mtx.RLock()
//releasing lock after return call
defer c.mtx.RUnlock()
// Checking if the value exists or not, and put the boolean value into "ok" variable
if _, ok := c.values[name]; ok {
return true
}
return false
}
func (c *container) Get(name string) interface{} {
//locks reading from c.values
c.mtx.RLock()
_, ok := c.values[name]
//unlocking it after reading and put the boolean value into the ok variable
c.mtx.RUnlock()
//if its false panic a error
if !ok {
panic(fmt.Sprintf("The service does not exist: %s", name))
}
//if panic is not triggered
//read lock services from reading
c.mtx.RLock()
//check if the name is in the services
_, ok = c.services[name]
//read unlock the service map
c.mtx.RUnlock()
// the ok (boolean) is false type define container as c in "v" variable
if !ok {
v := c.values[name](c)
c.mtx.RLock()
c.services[name] = v
c.mtx.RUnlock()
// it iterates over the extends map and ....
if extends, ok := c.extends[name]; ok {
for _, extend := range extends {
//creating an slice of reflect value
result := extend.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(c)})
c.mtx.Lock()
c.services[name] = result[0].Interface()
c.mtx.Unlock()
}
}
}
c.mtx.RLock()
defer c.mtx.RUnlock()
return c.services[name]
}
// Fill Returns error if anything happens
func (c *container) Fill(name string, dst interface{}) {
// getting the struct
obj := c.Get(name)
// added element the dst interface using fill funciton
if err := fill(obj, dst); err != nil {
log.Panic(err)
}
}
//Extend mainly have control over the c.extends , it is appending a callback function
func (c *container) Extend(name string, f ExtenderFunc) {
c.mtx.Lock()
defer c.mtx.Unlock()
if _, ok := c.services[name]; ok {
log.Panic("Cannnot extend initialized service")
}
if _, ok := c.values[name]; !ok {
log.Panicf("Cannot extend %s service", name)
}
c.extends[name] = append(c.extends[name], reflect.ValueOf(f))
}
// Get keys mainly creates a empty map , fill the map with all the value with string
//by appending and return the map
func (c *container) GetKeys() []string {
c.mtx.RLock()
defer c.mtx.RUnlock()
keys := make([]string, 0)
for k := range c.values {
keys = append(keys, k)
}
return keys
}
//fill method just add an element to the dest interface (which is being injected)
func fill(src, dest interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
d := reflect.TypeOf(dest)
s := reflect.TypeOf(src)
err = fmt.Errorf("The fill destination should be a pointer to a %s , but you used a %s", s, d)
}
}()
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
return err
}
the error message I'm getting, Any idea how to solve this?:
Cannot use 'func(c Container) interface{} { return &MyService{} }' (type func(c Container) interface{}) as type ContainerFunc
If I attempt to rebuild your situation (with Go 1.13) by constructing my own Framework
from guesses—it would help if you showed us what is in Framework
—the closest I can get is this:
./unit.go:25:22: cannot use func literal (type func(Container) interface {})
as type framework.ContainerFunc in argument to c.Set
which is due to this line:
type Container framework.Container
Changing it to:
type Container = framework.Container
so that this is an alias for the existing type makes the code build. (It would probably be better to just use framework.Container
where appropriate, rather than defining your own unit.Container
type.)
(If your IDE is dropping the framework.
prefix from framework.Container
, and otherwise shortening the Go compiler's error messages here, that would explain it.)