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