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