fork from github
This commit is contained in:
commit
d90ca3f007
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vscode
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Markus Morgenstern
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
99
README.md
Normal file
99
README.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# generic-di
|
||||||
|
|
||||||
|
Go Dependency Injection with Generics
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
configuration.go
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.apihub24.de/admin/generic-di"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// register the Struct Constructor Function for DI
|
||||||
|
di.Injectable(NewConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
UserName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfiguration() *Configuration {
|
||||||
|
return &Configuration{
|
||||||
|
UserName: "Markus",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
greeter.go
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.apihub24.de/admin/generic-di"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
di.Injectable(NewGreeter)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Greeter struct {
|
||||||
|
config *Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGreeter() *Greeter {
|
||||||
|
return &Greeter{
|
||||||
|
// here was the Configuration from configuration.go injected
|
||||||
|
config: di.Inject[*Configuration](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Greeter) Greet() string {
|
||||||
|
return fmt.Sprintf("Hello, %s", ctx.config.UserName)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
message_service.go
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.apihub24.de/admin/generic-di"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
di.Injectable(NewMessageService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageService struct {
|
||||||
|
greeter *Greeter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessageService() *MessageService {
|
||||||
|
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[*MessageService]()
|
||||||
|
// prints the message "Hello, Markus"
|
||||||
|
println(msgService.Welcome())
|
||||||
|
}
|
||||||
|
```
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module git.apihub24.de/admin/generic-di
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
|
|
||||||
|
require github.com/google/uuid v1.6.0
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
67
injector.go
Normal file
67
injector.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package di
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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
|
||||||
|
func Injectable[T any](creator func() T) {
|
||||||
|
creatorMutex.Lock()
|
||||||
|
defer creatorMutex.Unlock()
|
||||||
|
creators[getSelector[T]()] = func() any {
|
||||||
|
return creator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject gets or create a Instance of the Struct used the Injectable constructor Function
|
||||||
|
func Inject[T any](identifier ...string) T {
|
||||||
|
var nilResult T
|
||||||
|
selector := getSelector[T]()
|
||||||
|
instanceSelector := getSelector[T](identifier...)
|
||||||
|
_, instanceExists := instances[instanceSelector].(T)
|
||||||
|
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 instances[instanceSelector].(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Destroy[T any](identifier ...string) {
|
||||||
|
instanceMutex.Lock()
|
||||||
|
defer instanceMutex.Unlock()
|
||||||
|
instanceSelector := getSelector[T](identifier...)
|
||||||
|
delete(instances, instanceSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSelector[T any](identifier ...string) string {
|
||||||
|
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)
|
||||||
|
}
|
||||||
140
injector_test.go
Normal file
140
injector_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package di_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
di "git.apihub24.de/admin/generic-di"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
di.Injectable(newTextService)
|
||||||
|
di.Injectable(newMessageService)
|
||||||
|
di.Injectable(newConfiguration)
|
||||||
|
di.Injectable(newGreetingService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
configuration struct{}
|
||||||
|
messageService struct {
|
||||||
|
texts *textService
|
||||||
|
}
|
||||||
|
|
||||||
|
textService struct {
|
||||||
|
config *configuration
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
greetingService interface {
|
||||||
|
Greeting() string
|
||||||
|
TakeID() string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newConfiguration() *configuration {
|
||||||
|
return &configuration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextService() *textService {
|
||||||
|
return &textService{
|
||||||
|
config: di.Inject[*configuration](),
|
||||||
|
id: uuid.NewString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGreetingService() greetingService {
|
||||||
|
return newTextService()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMessageService() *messageService {
|
||||||
|
return &messageService{
|
||||||
|
texts: di.Inject[*textService](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *configuration) GetUserName() string {
|
||||||
|
return "Markus"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *textService) Greeting() string {
|
||||||
|
return fmt.Sprintf("Hello %s", ctx.config.GetUserName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *textService) GetID() string {
|
||||||
|
return ctx.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *textService) TakeID() string {
|
||||||
|
return ctx.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *messageService) GetTextServiceID() string {
|
||||||
|
return ctx.texts.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInject(t *testing.T) {
|
||||||
|
msg := newMessageService()
|
||||||
|
println(msg.texts.Greeting())
|
||||||
|
if msg.texts.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInject_Duplicate(t *testing.T) {
|
||||||
|
msg1 := newMessageService()
|
||||||
|
msg2 := newMessageService()
|
||||||
|
println(msg1.texts.Greeting())
|
||||||
|
println(msg2.texts.Greeting())
|
||||||
|
if msg1.texts.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
if msg2.texts.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
if msg1.GetTextServiceID() != msg2.GetTextServiceID() {
|
||||||
|
t.Errorf("expect same instance of textService")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInject_Parallel(t *testing.T) {
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
go func() {
|
||||||
|
println(di.Inject[*textService]().GetID())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInject_MultipleInstances(t *testing.T) {
|
||||||
|
textServiceA := di.Inject[*textService]("a")
|
||||||
|
textServiceB := di.Inject[*textService]("b")
|
||||||
|
if textServiceA.GetID() == textServiceB.GetID() {
|
||||||
|
t.Errorf("expect a seperate instance textServiceA and textServiceB but there was identical")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryInterface(t *testing.T) {
|
||||||
|
greeter := di.Inject[greetingService]()
|
||||||
|
if greeter.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTryInterface_MultipleInstances(t *testing.T) {
|
||||||
|
greeterA := di.Inject[greetingService]("a")
|
||||||
|
greeterB := di.Inject[greetingService]("b")
|
||||||
|
if greeterA.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
if greeterB.Greeting() != "Hello Markus" {
|
||||||
|
t.Errorf("expect greeting Hello Markus")
|
||||||
|
}
|
||||||
|
if greeterA.TakeID() == greeterB.TakeID() {
|
||||||
|
t.Errorf("expect greetingA and greeterB are the same Instance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestroy(t *testing.T) {
|
||||||
|
_ = di.Inject[textService]("a")
|
||||||
|
di.Destroy[textService]("a")
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user