Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

godi (Golang Dependency Injection Library)

A simple dependency injection library inspired by golobby

Requirements

godi requires go version 1.22 because it uses the newly added reflect.TypeFor[T].

Get example:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Get must match exactly. If the provider returns an pointer to a struct rather than an interface, even if that struct fulfils the interface, the lookup for the bindings will fail.

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typemyService:= di.Get[MyServiceInterface](injector)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysaService:=di.Get[MyServiceInterface](injector, "a")
bService:=di.Get[MyServiceInterface](injector, "b")

Resolve / NamedResolve examples:

Here we register a singleton provider (called once and the value is shared) to resolve an interface to it's provided the concrete value.

Note: the return value of the provider and the argument type passed to Resolve must match. Note: arguments to Resolve / NamedResolvemust be passed by reference (including interfaces and pointers)

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
// resolve the interface to its concrete typevarmyServiceMyServiceInterfaceinjector.Resolve(&myService)
// you can register providers under string keysinjector.NamedSingleton("a", func () MyServiceInterface {
return&ServiceImplA{}
})
injector.NamedSingleton("b", func () MyServiceInterface {
return&ServiceImplB{}
})
// and resolve from these keysvaraService, bServiceMyServiceInterfaceinjector.NamedResolve("a", &aService)
injector.NamedResolve("b", &bService)

Fill examples:

Here we register an "instance" provider (called for each dependency, all values are unique) and use it to "fill" fields of an parent struct based on type.

Note: the return value of the provider and the tagged fields must match. Note: structs may be passed as pointers or references of pointers.

typeNestedType {
valint
}
typeAnotherType {
valint
}
typeMyStruct {
nested*NestedType`di:"type"`value*AnotherType*`di:"type"`
}
injector:=di.NewInjector() // or di.GlobalInjector// register an "instance" providerinjector.Instance(func () *AnotherType* {
returnAnotherType{
val: 12,
}
})
injector.Instance(func () *NestedType {
return&NestedType{
val: 42,
}
})
// fill the fields by "type"myStruct:=&MyStruct{}
injector.Fill(myStruct)
fmt.Println(myStruct.nested.val) // "42"fmt.Println(myStruct.value.val) // "12"// we can can also fill by name:typeMyOtherStructstruct {
a*NestedType`di:"name"`b*NestedType`di:"name"`
}
injector.NamedInstance("a", func () *NestedType {
return&NestedType{
val: 123,
}
})
injector.NamedInstance("b", func () *NestedType {
return&NestedType{
val: 456,
}
})
myOtherStruct:=&MyOtherStruct{}
injector.Fill(&myOtherStruct) // passing the reference to the pointer is okayfmt.Println(myOtherStruct.a.val) // "123"fmt.Println(myOtherStruct.b.val) // "456"

Call example:

You can invoke the injector to give you a concrete type for provided closure:

injector:=di.NewInjector() // or di.GlobalInjector// register a "singleton" providerinjector.Singleton(func () MyServiceInterface {
return&myServiceImpl{}
})
injector.Call(func (svcMyServiceInterface) {
svc.DoWhatever()
})

Singletons vs Instances providers

Singleton providers will be executed once and the resulting instance will be shared between all injections. These are ideal for stateless and/or threadsafe constructs. Singleton providers are evaluated lazily which means the provider is not called until the moment of injection.

Instance providers will be executed for each injection and the resulting instances will not shared between injections. These are ideal for stateful or non-threadsafe constructs that should not be shared.

Circular dependencies

godi handles circular dependencies for both singleton and instance methods. For singletons the cyclic properties will all point to the same resolved singletonvalues. For cyclic instance instantiation, instances will point to the same resolved values within the injection call.

typeAstruct {
B*B`di:"type"`
}
typeBstruct {
A*A `di:"type"
}
injector.Singleton(func () *A {
return &A{}
})
injector.Instance(func () *B {
return &B{}
})
a := di.Get[*A](injector)
fmt.Println(a.B.A.B.A.B != nil) // true

Thread safety:

Do not register providers via Singleton, NamedSingleton, Instance, and NamedInstace while at the same time calling Fill or Resolve or Call from a separate goroutine.

The Fill or Resolve or Call methods read from a map of bindings, and the Singleton, NamedSingleton, Instance, and NamedInstace methods write to that map of bindings.

This shouldn't every really happen, since you would always want to define all your providers sequentially at the startup of an application before use.

Everything else is threadsafe and can be called across goroutines.

Debugging:

Verbose debug logs can be enabled / disabled and can be useful when debugging injection failures.

Ex.

injector.EnableDebugLogging() // <-- add this in if you are dealing with some tricky di errorsdeferinjector.DisableDebugLogging()
s:=injector.Get[*Something]()

As the logs are very verbose, it is recommended that the enable / disable calls be scoped as tightly to the source of error as possible.

Handling errors:

By default, if there is an internal error encountered by the di package, it will panic. To capture and handle errors you can provide an error handler:

injector:=di.NewInjector()
injector.SetErrorHandler(func (errerror) {
// log the error
})

A injection error should be considered "fatal" and should be resolved in development / QA. They are not events to be handeld gracefully in production.

Acknowledgements

  • A huge thank you to Kevin Birk for the design and contributions to this repo.
  • Thanks to golobby for inspiring this repo and serving as a code reference for how to use the reflect package to accomplish sane dependency injection in go.

About

Golang Dependency Injector module

Resources

Contributing

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages