Container: create service container and service provider
This commit is contained in:
parent
722e8191a9
commit
f866f18cfd
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user