Container: create service container and service provider
This commit is contained in:
		
							
								
								
									
										195
									
								
								framework/container.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								framework/container.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,195 @@
 | 
			
		||||
package framework
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Container interface {
 | 
			
		||||
	// Bind binds a service provider to the container.
 | 
			
		||||
	//
 | 
			
		||||
	// If the service exists, the old one will be replaced by the new one.
 | 
			
		||||
	// No error is returned in this case.
 | 
			
		||||
	//
 | 
			
		||||
	// Return any Init() error.
 | 
			
		||||
	Bind(provider ServiceProvider) error
 | 
			
		||||
 | 
			
		||||
	// IsBound returns true the provider is bound to the container.
 | 
			
		||||
	IsBound(name string) bool
 | 
			
		||||
 | 
			
		||||
	// Get gets or creates a service by its name.
 | 
			
		||||
	//
 | 
			
		||||
	// Return error if the service cannot be initiated or doesn't exist.
 | 
			
		||||
	Get(name string) (interface{}, error)
 | 
			
		||||
 | 
			
		||||
	// JustGet gets a service, return nil if error.
 | 
			
		||||
	JustGet(name string) interface{}
 | 
			
		||||
 | 
			
		||||
	// GetExisted gets an initiated service by its name.
 | 
			
		||||
	GetExisted(name string) interface{}
 | 
			
		||||
 | 
			
		||||
	// MakeNew creates a new instance of the service with different parameters.
 | 
			
		||||
	//
 | 
			
		||||
	// Used for non singleton services.
 | 
			
		||||
	MakeNew(name string, params []interface{}) (interface{}, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GoWebContainer struct {
 | 
			
		||||
	// Must implement Container interfaces.
 | 
			
		||||
	Container
 | 
			
		||||
 | 
			
		||||
	// Service providers by name.
 | 
			
		||||
	providers map[string]ServiceProvider
 | 
			
		||||
 | 
			
		||||
	// Instantiated services by name.
 | 
			
		||||
	instances map[string]interface{}
 | 
			
		||||
 | 
			
		||||
	// RWMutex to protect the changes in container.
 | 
			
		||||
	mu sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func errUnknownService(name string) error {
 | 
			
		||||
	return fmt.Errorf("service %q is not registered in the container", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewGoWebContainer() *GoWebContainer {
 | 
			
		||||
	return &GoWebContainer{
 | 
			
		||||
		providers: map[string]ServiceProvider{},
 | 
			
		||||
		instances: map[string]interface{}{},
 | 
			
		||||
		mu:        sync.RWMutex{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) PrintProviders(w io.Writer) {
 | 
			
		||||
	ret := make([]string, len(c.providers))
 | 
			
		||||
	for _, provider := range c.providers {
 | 
			
		||||
		name := provider.Name()
 | 
			
		||||
 | 
			
		||||
		ret = append(ret, name)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintf(w, "%v", ret)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) Bind(provider ServiceProvider) error {
 | 
			
		||||
	c.bindProvider(provider)
 | 
			
		||||
	// Instantiate Later ?
 | 
			
		||||
	if provider.InstantiateLater() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Instantiate now !
 | 
			
		||||
	_, err := c.instantiate(provider, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) bindProvider(provider ServiceProvider) {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	name := provider.Name()
 | 
			
		||||
	c.providers[name] = provider
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) IsBound(name string) bool {
 | 
			
		||||
	return c.getServiceProvider(name) != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) getServiceProvider(name string) ServiceProvider {
 | 
			
		||||
	c.mu.RLock()
 | 
			
		||||
	defer c.mu.RUnlock()
 | 
			
		||||
 | 
			
		||||
	provider, ok := c.providers[name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		return provider
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) Get(name string) (interface{}, error) {
 | 
			
		||||
	return c.makeServiceInstance(name, nil, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) GetExisted(name string) interface{} {
 | 
			
		||||
	return c.getServiceInstance(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) JustGet(name string) interface{} {
 | 
			
		||||
	ins, err := c.makeServiceInstance(name, nil, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return ins
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) MakeNew(name string, params []interface{}) (interface{}, error) {
 | 
			
		||||
	return c.makeServiceInstance(name, params, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) getServiceInstance(name string) interface{} {
 | 
			
		||||
	c.mu.RLock()
 | 
			
		||||
	defer c.mu.RUnlock()
 | 
			
		||||
 | 
			
		||||
	instance, ok := c.instances[name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		return instance
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *GoWebContainer) makeServiceInstance(
 | 
			
		||||
	name string,
 | 
			
		||||
	params []interface{},
 | 
			
		||||
	forceNew bool,
 | 
			
		||||
) (interface{}, error) {
 | 
			
		||||
	provider := c.getServiceProvider(name)
 | 
			
		||||
	if provider == nil {
 | 
			
		||||
		return nil, errUnknownService(name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if forceNew {
 | 
			
		||||
		return c.instantiate(provider, params)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	instance := c.getServiceInstance(name)
 | 
			
		||||
 | 
			
		||||
	if instance != nil {
 | 
			
		||||
		return instance, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create a new instance
 | 
			
		||||
	return c.instantiate(provider, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// instantiate instantiates a new instance.
 | 
			
		||||
// If no params are provided, then default params are used.
 | 
			
		||||
func (c *GoWebContainer) instantiate(
 | 
			
		||||
	provider ServiceProvider,
 | 
			
		||||
	params []interface{},
 | 
			
		||||
) (interface{}, error) {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if err := provider.Init(c); err != nil {
 | 
			
		||||
		// TODO: Error should be wrapped.
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		params = provider.Params(c)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	construct := provider.Register(c)
 | 
			
		||||
	instance, err := construct(params...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// TODO: Error should be wrapped.
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.instances[provider.Name()] = instance
 | 
			
		||||
 | 
			
		||||
	return instance, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								framework/containter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								framework/containter_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
			
		||||
package framework
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// {{{ contract
 | 
			
		||||
 | 
			
		||||
const DummyProviderName = "container:test"
 | 
			
		||||
 | 
			
		||||
type IDummyService interface {
 | 
			
		||||
	GetDummy() DummyStruct
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DummyStruct struct {
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ provider
 | 
			
		||||
type DummyServiceProvider struct{}
 | 
			
		||||
 | 
			
		||||
func (p *DummyServiceProvider) Name() string {
 | 
			
		||||
	return DummyProviderName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DummyServiceProvider) Register(c Container) NewInstance {
 | 
			
		||||
	return NewDummyService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DummyServiceProvider) Init(c Container) error {
 | 
			
		||||
	log.Println("init")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DummyServiceProvider) InstantiateLater() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *DummyServiceProvider) Params(c Container) []interface{} {
 | 
			
		||||
	return []interface{}{c}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
// {{{ service
 | 
			
		||||
 | 
			
		||||
type DummyService struct {
 | 
			
		||||
	IDummyService
 | 
			
		||||
 | 
			
		||||
	// parameters
 | 
			
		||||
	c Container
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DummyService) GetDummy() DummyStruct {
 | 
			
		||||
	return DummyStruct{
 | 
			
		||||
		Name: "Dummy!",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDummyService(params ...interface{}) (interface{}, error) {
 | 
			
		||||
	c := params[0].(Container)
 | 
			
		||||
 | 
			
		||||
	log.Println("new dummy service")
 | 
			
		||||
 | 
			
		||||
	return &DummyService{c: c}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// }}}
 | 
			
		||||
 | 
			
		||||
func TestBind(t *testing.T) {
 | 
			
		||||
	container := NewGoWebContainer()
 | 
			
		||||
	provider := &DummyServiceProvider{}
 | 
			
		||||
 | 
			
		||||
	container.Bind(provider)
 | 
			
		||||
 | 
			
		||||
	dummyService := container.JustGet(DummyProviderName).(IDummyService)
 | 
			
		||||
 | 
			
		||||
	want := DummyStruct{
 | 
			
		||||
		Name: "Dummy!",
 | 
			
		||||
	}
 | 
			
		||||
	got := dummyService.GetDummy()
 | 
			
		||||
 | 
			
		||||
	if !reflect.DeepEqual(want, got) {
 | 
			
		||||
		t.Errorf("want %v, got %v ", want, got)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								framework/provider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								framework/provider.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
package framework
 | 
			
		||||
 | 
			
		||||
// NewInstance defines the function to create a new instance for a service.
 | 
			
		||||
type NewInstance func(...interface{}) (interface{}, error)
 | 
			
		||||
 | 
			
		||||
// ServiceProvider is the interface for a service provider
 | 
			
		||||
type ServiceProvider interface {
 | 
			
		||||
	// Register a service into the service container, return a NewInstance constructer
 | 
			
		||||
	Register(Container) NewInstance
 | 
			
		||||
 | 
			
		||||
	// Init is called when instantiating the service.
 | 
			
		||||
	Init(Container) error
 | 
			
		||||
 | 
			
		||||
	// InstantiateLater decides if the service is instantiated at register phase.
 | 
			
		||||
	// If true, then this service shall be instantiated later.
 | 
			
		||||
	InstantiateLater() bool
 | 
			
		||||
 | 
			
		||||
	// Params defines the parameters needed to instantiate a service.
 | 
			
		||||
	Params(Container) []interface{}
 | 
			
		||||
 | 
			
		||||
	// Name is the name of the service provider
 | 
			
		||||
	Name() string
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user