Skip to content

Instantly share code, notes, and snippets.

@themsaid
Created February 24, 2025 06:18
Show Gist options
  • Select an option

  • Save themsaid/d5bf5f665393c6370a2302ec2086e4a3 to your computer and use it in GitHub Desktop.

Select an option

Save themsaid/d5bf5f665393c6370a2302ec2086e4a3 to your computer and use it in GitHub Desktop.
A Dependency Injection Container for Go. Explained here here: https://themsaid.com/building-a-dependency-injection-container-for-go
package astra
import (
"fmt"
"reflect"
"sync"
)
type Container struct {
mu sync.RWMutex
services map[string]any
bootstrappers []func()
}
type serviceProvider[T any] func(*Container) (T, error)
type containerService[T any] struct {
mu sync.Mutex
instance T
made bool
provider serviceProvider[T]
}
func NewContainer(size int) *Container {
return &Container{
services: make(map[string]any, size),
mu: sync.RWMutex{},
bootstrappers: make([]func(), 0, size),
}
}
func (s *containerService[T]) make(c *Container) (T, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.made {
return s.instance, nil
}
instance, err := s.provider(c)
if err != nil {
return instance, err
}
s.instance = instance
s.made = true
return instance, nil
}
func Register[T any](c *Container, provider serviceProvider[T]) {
c.mu.Lock()
defer c.mu.Unlock()
c.services[serviceName[T]()] = &containerService[T]{
provider: provider,
mu: sync.Mutex{},
}
c.bootstrappers = append(c.bootstrappers, func() {
Make[T](c)
})
}
func RegisterDeferred[T any](c *Container, provider serviceProvider[T]) {
c.mu.Lock()
defer c.mu.Unlock()
c.services[serviceName[T]()] = &containerService[T]{
provider: provider,
mu: sync.Mutex{},
}
}
func Make[T any](c *Container) T {
c.mu.RLock()
defer c.mu.RUnlock()
name := serviceName[T]()
anything, ok := c.services[name]
if !ok {
panic(fmt.Sprintf("failed to make service[%s]: not found", name))
}
service, ok := anything.(*containerService[T])
if !ok {
panic(fmt.Sprintf("failed to make service[%s]: type assertion failed", name))
}
instance, err := service.make(c)
if err != nil {
panic(fmt.Sprintf("failed to make service[%s]: %s", name, err))
}
return instance
}
func (c *Container) Bootstrap() {
for _, bootstrapper := range c.bootstrappers {
bootstrapper()
}
c.bootstrappers = nil
}
func serviceName[T any]() string {
typeForT := reflect.TypeFor[T]()
if typeForT.Name() != "" {
return typeForT.PkgPath() + "." + typeForT.Name()
}
typeForV := typeForT.Elem()
if typeForT.Kind() == reflect.Pointer {
return "*" + typeForV.PkgPath() + "." + typeForV.Name()
}
panic("unsupported type")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment