Compare commits
No commits in common. "main" and "v1.3.0" have entirely different histories.
124
README.md
124
README.md
@ -4,8 +4,6 @@ Go Dependency Injection with Generics
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
### Struct
|
|
||||||
|
|
||||||
configuration.go
|
configuration.go
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -99,125 +97,3 @@ func main() {
|
|||||||
println(msgService.Welcome())
|
println(msgService.Welcome())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Interface
|
|
||||||
|
|
||||||
message_service.go
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.apihub24.de/admin/generic-di"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
di.Injectable(newMessageService)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IMessageService interface {
|
|
||||||
Welcome() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageService struct {
|
|
||||||
greeter *Greeter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessageService() IMessageService {
|
|
||||||
return &messageService{
|
|
||||||
// here was the Greeter from greeter.go injected
|
|
||||||
greeter: di.Inject[*Greeter](),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *messageService) Welcome() string {
|
|
||||||
return ctx.greeter.Greet()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
main.go
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import di "git.apihub24.de/admin/generic-di"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
msgService := di.Inject[IMessageService]()
|
|
||||||
// prints the message "Hello, Markus"
|
|
||||||
println(msgService.Welcome())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Replace Instance
|
|
||||||
|
|
||||||
services/message_service.go
|
|
||||||
|
|
||||||
```go
|
|
||||||
package services
|
|
||||||
|
|
||||||
import "git.apihub24.de/admin/generic-di"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
di.Injectable(newMessageService)
|
|
||||||
}
|
|
||||||
|
|
||||||
type IMessageService interface {
|
|
||||||
Welcome() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageService struct {
|
|
||||||
greeter *Greeter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessageService() IMessageService {
|
|
||||||
return &messageService{
|
|
||||||
// here was the Greeter from greeter.go injected
|
|
||||||
greeter: di.Inject[*Greeter](),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *messageService) Welcome() string {
|
|
||||||
return ctx.greeter.Greet()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
main.go
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import di "git.apihub24.de/admin/generic-di"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
msgService := di.Inject[services.IMessageService]()
|
|
||||||
// prints the message "Hello, Markus"
|
|
||||||
println(msgService.Welcome())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
message_service_test.go
|
|
||||||
|
|
||||||
```go
|
|
||||||
package services_test
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// replace the instance of IMessageService with the Mock
|
|
||||||
di.Replace(newMessageServiceMock)
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageServiceMock struct {}
|
|
||||||
|
|
||||||
func newMessageServiceMock() services.IMessageService {
|
|
||||||
return &messageServiceMock{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *messageServiceMock) Welcome() string {
|
|
||||||
return "Hello, Mock"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMessageServiceMocking(t *testing.T) {
|
|
||||||
service := di.Inject[services.IMessageService]()
|
|
||||||
if service.Welcome() != "Hello, Mock" {
|
|
||||||
t.Errorf("expect Hello, Mock but get %s", service.Welcome())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
103
container.go
103
container.go
@ -1,103 +0,0 @@
|
|||||||
package di
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type container struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
creators map[string]func() any
|
|
||||||
instances map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
var globalContainer *container
|
|
||||||
var once sync.Once
|
|
||||||
|
|
||||||
func getContainer() *container {
|
|
||||||
once.Do(func() {
|
|
||||||
globalContainer = &container{
|
|
||||||
mu: sync.RWMutex{},
|
|
||||||
creators: make(map[string]func() any),
|
|
||||||
instances: make(map[string]any),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return globalContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) injectable(typ reflect.Type, creator func() any) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
selector := c.getSelector(typ)
|
|
||||||
c.creators[selector] = creator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) replace(typ reflect.Type, creator func() any, identifier ...string) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
selector := c.getSelector(typ)
|
|
||||||
instanceSelector := c.getSelector(typ, identifier...)
|
|
||||||
c.creators[selector] = creator
|
|
||||||
createdInstance := creator()
|
|
||||||
c.instances[instanceSelector] = createdInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) inject(typ reflect.Type, identifier ...string) (any, bool) {
|
|
||||||
instanceSelector := c.getSelector(typ, identifier...)
|
|
||||||
|
|
||||||
c.mu.RLock()
|
|
||||||
if instance, ok := c.instances[instanceSelector]; ok {
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return instance, true
|
|
||||||
}
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
c.mu.RLock()
|
|
||||||
if instance, ok := c.instances[instanceSelector]; ok {
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return instance, true
|
|
||||||
}
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
selector := c.getSelector(typ)
|
|
||||||
creator, creatorExists := c.creators[selector]
|
|
||||||
if !creatorExists {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
createdInstance := creator()
|
|
||||||
c.mu.Lock()
|
|
||||||
c.instances[instanceSelector] = createdInstance
|
|
||||||
c.mu.Unlock()
|
|
||||||
return createdInstance, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) destroy(typ reflect.Type, identifier ...string) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
instanceSelector := c.getSelector(typ, identifier...)
|
|
||||||
delete(c.instances, instanceSelector)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) destroyAllMatching(match func(string) bool) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for key := range c.instances {
|
|
||||||
if match(key) {
|
|
||||||
delete(c.instances, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println("")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *container) getSelector(typ reflect.Type, identifier ...string) string {
|
|
||||||
typeName := typ.String()
|
|
||||||
additionalKey := strings.Join(identifier, "_")
|
|
||||||
return fmt.Sprintf("%s_%s", additionalKey, typeName)
|
|
||||||
}
|
|
||||||
66
injector.go
66
injector.go
@ -1,41 +1,67 @@
|
|||||||
package di
|
package di
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var creatorMutex = sync.Mutex{}
|
||||||
|
var instanceMutex = sync.Mutex{}
|
||||||
|
var creators = make(map[string]func() any)
|
||||||
|
var instances = make(map[string]any)
|
||||||
|
|
||||||
// Injectable marks a constructor Function of a Struct for DI
|
// Injectable marks a constructor Function of a Struct for DI
|
||||||
func Injectable[T any](creator func() T) {
|
func Injectable[T any](creator func() T) {
|
||||||
typ := reflect.TypeOf((*T)(nil)).Elem()
|
creatorMutex.Lock()
|
||||||
getContainer().injectable(typ, func() any { return creator() })
|
defer creatorMutex.Unlock()
|
||||||
|
creators[getSelector[T]()] = func() any {
|
||||||
|
return creator()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Replace[T any](creator func() T, identifier ...string) {
|
|
||||||
typ := reflect.TypeOf((*T)(nil)).Elem()
|
|
||||||
getContainer().replace(typ, func() any { return creator() })
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReplaceInstance[T any](instance T, identifier ...string) {
|
|
||||||
Replace(func() T { return instance }, identifier...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject gets or create a Instance of the Struct used the Injectable constructor Function
|
// Inject gets or create a Instance of the Struct used the Injectable constructor Function
|
||||||
func Inject[T any](identifier ...string) T {
|
func Inject[T any](identifier ...string) T {
|
||||||
var result T
|
var nilResult T
|
||||||
typ := reflect.TypeOf((*T)(nil)).Elem()
|
selector := getSelector[T]()
|
||||||
if instance, ok := getContainer().inject(typ, identifier...); ok {
|
instanceSelector := getSelector[T](identifier...)
|
||||||
if result, ok = instance.(T); ok {
|
_, instanceExists := instances[instanceSelector].(T)
|
||||||
return result
|
if !instanceExists {
|
||||||
|
creator, creatorExists := creators[selector]
|
||||||
|
if !creatorExists {
|
||||||
|
return nilResult
|
||||||
|
}
|
||||||
|
createdInstance, instanceCreated := creator().(T)
|
||||||
|
if instanceCreated {
|
||||||
|
instanceMutex.Lock()
|
||||||
|
defer instanceMutex.Unlock()
|
||||||
|
instance, instanceExists := instances[instanceSelector].(T)
|
||||||
|
if instanceExists {
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
instances[instanceSelector] = createdInstance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return instances[instanceSelector].(T)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Destroy[T any](identifier ...string) {
|
func Destroy[T any](identifier ...string) {
|
||||||
typ := reflect.TypeOf((*T)(nil)).Elem()
|
instanceMutex.Lock()
|
||||||
getContainer().destroy(typ, identifier...)
|
defer instanceMutex.Unlock()
|
||||||
|
instanceSelector := getSelector[T](identifier...)
|
||||||
|
delete(instances, instanceSelector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DestroyAllMatching(match func(string) bool) {
|
func getSelector[T any](identifier ...string) string {
|
||||||
getContainer().destroyAllMatching(match)
|
var def T
|
||||||
|
typeName := ""
|
||||||
|
typeOf := reflect.TypeOf(def)
|
||||||
|
if typeOf != nil {
|
||||||
|
typeName = typeOf.String()
|
||||||
|
} else {
|
||||||
|
typeName = reflect.TypeOf((*T)(nil)).Elem().String()
|
||||||
|
}
|
||||||
|
additionalKey := strings.Join(identifier, "_")
|
||||||
|
return fmt.Sprintf("%s_%s", typeName, additionalKey)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package di_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
di "git.apihub24.de/admin/generic-di"
|
di "git.apihub24.de/admin/generic-di"
|
||||||
@ -14,7 +13,6 @@ func init() {
|
|||||||
di.Injectable(newMessageService)
|
di.Injectable(newMessageService)
|
||||||
di.Injectable(newConfiguration)
|
di.Injectable(newConfiguration)
|
||||||
di.Injectable(newGreetingService)
|
di.Injectable(newGreetingService)
|
||||||
di.Injectable(newBasicOverridableService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -32,33 +30,8 @@ type (
|
|||||||
Greeting() string
|
Greeting() string
|
||||||
TakeID() string
|
TakeID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
overridableService interface {
|
|
||||||
GetInstanceID() string
|
|
||||||
GetValue() string
|
|
||||||
}
|
|
||||||
|
|
||||||
basicOverridableService struct {
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
basicOverridableServiceMock struct {
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newBasicOverridableService() overridableService {
|
|
||||||
return &basicOverridableService{
|
|
||||||
id: uuid.NewString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBasicOverridableServiceMock() overridableService {
|
|
||||||
return &basicOverridableServiceMock{
|
|
||||||
id: uuid.NewString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfiguration() *configuration {
|
func newConfiguration() *configuration {
|
||||||
return &configuration{}
|
return &configuration{}
|
||||||
}
|
}
|
||||||
@ -80,22 +53,6 @@ func newMessageService() *messageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *basicOverridableService) GetInstanceID() string {
|
|
||||||
return ctx.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *basicOverridableService) GetValue() string {
|
|
||||||
return "i am original"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *basicOverridableServiceMock) GetInstanceID() string {
|
|
||||||
return ctx.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *basicOverridableServiceMock) GetValue() string {
|
|
||||||
return "i am mock"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *configuration) GetUserName() string {
|
func (ctx *configuration) GetUserName() string {
|
||||||
return "Markus"
|
return "Markus"
|
||||||
}
|
}
|
||||||
@ -117,9 +74,6 @@ func (ctx *messageService) GetTextServiceID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInject(t *testing.T) {
|
func TestInject(t *testing.T) {
|
||||||
// testMutex.Lock()
|
|
||||||
// defer testMutex.Unlock()
|
|
||||||
|
|
||||||
msg := newMessageService()
|
msg := newMessageService()
|
||||||
println(msg.texts.Greeting())
|
println(msg.texts.Greeting())
|
||||||
if msg.texts.Greeting() != "Hello Markus" {
|
if msg.texts.Greeting() != "Hello Markus" {
|
||||||
@ -128,9 +82,6 @@ func TestInject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInject_Duplicate(t *testing.T) {
|
func TestInject_Duplicate(t *testing.T) {
|
||||||
// testMutex.Lock()
|
|
||||||
// defer testMutex.Unlock()
|
|
||||||
|
|
||||||
msg1 := newMessageService()
|
msg1 := newMessageService()
|
||||||
msg2 := newMessageService()
|
msg2 := newMessageService()
|
||||||
println(msg1.texts.Greeting())
|
println(msg1.texts.Greeting())
|
||||||
@ -183,42 +134,6 @@ func TestTryInterface_MultipleInstances(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverwriteInjectable(t *testing.T) {
|
|
||||||
basic := di.Inject[overridableService]()
|
|
||||||
basicID := basic.GetInstanceID()
|
|
||||||
if basic.GetValue() != "i am original" {
|
|
||||||
t.Errorf("wrong service instance get")
|
|
||||||
}
|
|
||||||
di.Replace(newBasicOverridableServiceMock)
|
|
||||||
basic = di.Inject[overridableService]()
|
|
||||||
if basic.GetInstanceID() == basicID {
|
|
||||||
t.Errorf("basic and newOne are the same instance")
|
|
||||||
}
|
|
||||||
if basic.GetValue() != "i am mock" {
|
|
||||||
t.Errorf("service not overwritten")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOverwriteInjectableInstance(t *testing.T) {
|
|
||||||
basic := di.Inject[overridableService]()
|
|
||||||
basicID := basic.GetInstanceID()
|
|
||||||
di.ReplaceInstance(newBasicOverridableServiceMock())
|
|
||||||
basic = di.Inject[overridableService]()
|
|
||||||
if basic.GetInstanceID() == basicID {
|
|
||||||
t.Errorf("basic and newOne are the same instance")
|
|
||||||
}
|
|
||||||
if basic.GetValue() != "i am mock" {
|
|
||||||
t.Errorf("service not overwritten")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDestroyMatching(t *testing.T) {
|
|
||||||
_ = di.Inject[greetingService]("abc")
|
|
||||||
_ = di.Inject[greetingService]("def")
|
|
||||||
_ = di.Inject[greetingService]("abc_def")
|
|
||||||
di.DestroyAllMatching(func(key string) bool { return strings.HasPrefix(key, "abc") })
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDestroy(t *testing.T) {
|
func TestDestroy(t *testing.T) {
|
||||||
_ = di.Inject[textService]("a")
|
_ = di.Inject[textService]("a")
|
||||||
di.Destroy[textService]("a")
|
di.Destroy[textService]("a")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user