ore

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 7, 2026 License: MIT Imports: 10 Imported by: 0

README

Ore — Dependency Injection for Go

Go Reference Go Report Card Mentioned in Awesome Go codecov

Ore is a lightweight, type-safe dependency injection (DI) container for Go. Inspired by ASP.NET's DI model, it gives you clean lifetime management, lazy initialization, runtime value injection, and modular containers — without magic or reflection soup.


Table of Contents

  1. Why Ore?
  2. Installation
  3. Core Concepts
  4. Lifetimes
  1. Registering Services
  1. Resolving Services
  1. Keyed Services
  2. Aliases
  3. Placeholder Services
  4. Isolated Containers
  5. Validation
  6. Graceful Termination
  7. Recommended Startup Pattern
  8. Real-World Usage Patterns
  9. API Reference

Why Ore?

Go encourages simplicity, and many projects wire dependencies by hand. That works — until it doesn't. As applications grow, manual wiring becomes error-prone, lifetime bugs sneak in, and test setup becomes tedious.

Ore solves this without going overboard:

  • Type-safe — powered by Go generics, no interface{} casting
  • Lifetime-aware — Singleton, Scoped, and Transient, correctly enforced
  • Context-native — scoped instances live and die with context.Context
  • Validates your graph — catches circular dependencies, missing registrations, and lifetime mismatches at startup
  • Modular — isolated containers per module for clean monolith architecture
  • Non-invasive — your structs don't need to embed anything from Ore

Installation

go get -u github.com/firasdarwish/ore
import "github.com/firasdarwish/ore"

Core Concepts

Ore works in two phases:

1. Registration — at startup, you tell Ore how to build each service and what lifetime it should have.

2. Resolution — at runtime, you ask Ore for a service. It constructs it (or returns a cached instance, depending on lifetime), injects its dependencies, and returns it to you.

The key insight: you never call new(MyService) scattered throughout your app. Ore centralizes construction, so lifetimes are predictable and dependencies are explicit.


Lifetimes

Every service registered with Ore has a lifetime that controls when instances are created and how long they live.

Singleton

A singleton is created once and reused for the entire application lifetime. Use this for stateless services, configuration, database connection pools, loggers, and anything that's safe to share globally.

App starts → instance created → reused forever → app shuts down

There are two flavors:

Eager singleton — created immediately at registration time. Best for critical services where you want startup failures to surface early.

Lazy singleton — created the first time it's resolved. Best for services that may never be used, or that are expensive to initialize.

Scoped

A scoped service is created once per context (context.Context). Every call to Get with the same context returns the same instance. A new context gets a fresh instance.

Request A context → instance A (shared within request A)
Request B context → instance B (shared within request B)

This is ideal for HTTP request handlers, database transactions, and anything that should be consistent within a single unit of work but isolated from other units.

Transient

A transient service is created fresh on every resolution. No caching, no sharing.

Get() → new instance
Get() → another new instance

Use transients for lightweight, stateful objects where sharing would cause bugs.


Registering Services

Eager Singleton

Pass an already-constructed instance directly. Ore stores it and returns it on every Get.

type Logger interface {
    Log(msg string)
}

type zapLogger struct{}
func (z *zapLogger) Log(msg string) { /* ... */ }

// Registered immediately — no construction function needed
ore.RegisterSingleton[Logger](&zapLogger{})
Anonymous Functions (RegisterFunc)

Pass a constructor function. Ore calls it when the service is first needed (or on every Get for transients). The function receives a context.Context and returns the service plus the (potentially updated) context.

ore.RegisterFunc[Logger](ore.Singleton, func(ctx context.Context) (Logger, context.Context) {
    return &zapLogger{}, ctx
})

Injecting dependencies inside a constructor:

When one service depends on another, call ore.Get inside the constructor and pass the context through. This is what threads scoped state correctly across a dependency chain.

type UserService interface {
    GetUser(id string) (*User, error)
}

type userServiceImpl struct {
    db DB
}

ore.RegisterFunc[UserService](ore.Scoped, func(ctx context.Context) (UserService, context.Context) {
    // Resolve DB — passes ctx through so scoped instances are shared
    db, ctx := ore.Get[DB](ctx)
    return &userServiceImpl{db: db}, ctx
})

Always pass ctx through the dependency chain. Scoped services store their instances in the context. If you don't thread the returned ctx forward, scoped dependencies won't be shared correctly within the same scope.

Creator[T] Interface (RegisterCreator)

An alternative to anonymous functions. Implement the Creator[T] interface on your struct, and Ore calls New to construct it.

type Creator[T any] interface {
    New(ctx context.Context) (T, context.Context)
}

This is useful when you want the struct itself to own its construction logic, keeping it colocated with the type definition.

type simpleCounter struct {
    count int
}

// Implementing Creator[Counter]
func (c *simpleCounter) New(ctx context.Context) (Counter, context.Context) {
    return &simpleCounter{count: 0}, ctx
}

func (c *simpleCounter) AddOne()       { c.count++ }
func (c *simpleCounter) GetCount() int { return c.count }

// Register using RegisterCreator
ore.RegisterCreator[Counter](ore.Scoped, &simpleCounter{})

Anonymous func vs Creator[T] — when to use which:

RegisterFunc RegisterCreator
Constructor location Inline at registration site On the struct itself
Good for External types, simple wiring Types that own their construction
Coupling to Ore None Struct knows about context.Context
Verbosity Slightly more boilerplate Cleaner registration call

Resolving Services

Get

Resolves a single service. Returns the service and an updated context (important for scoped services).

ctx := context.Background()

logger, ctx := ore.Get[Logger](ctx)
logger.Log("hello")

Always use the returned ctx for subsequent resolutions if any services in the chain are scoped.

GetList

Resolves all registered implementations of a type. Returns a slice.

// If you registered three different Counter implementations:
counters, ctx := ore.GetList[Counter](ctx)
for _, c := range counters {
    fmt.Println(c.GetCount())
}

GetList is useful for plugin-style architectures or when you genuinely need all implementations (e.g., firing all event handlers, running all validators).

GetList never panics if nothing is registered — it returns an empty slice.


Keyed Services

Keys let you register multiple implementations of the same type and select among them by name at resolution time.

type Greeter interface {
    Greet() string
}

type FriendlyGreeter struct{}
func (g *FriendlyGreeter) Greet() string { return "Hey! Great to see you!" }

type FormalGreeter struct{}
func (g *FormalGreeter) Greet() string { return "Good day. How may I assist you?" }

// Register with keys
ore.RegisterKeyedFunc[Greeter](ore.Scoped, func(ctx context.Context) (Greeter, context.Context) {
    return &FriendlyGreeter{}, ctx
}, "friendly")

ore.RegisterKeyedFunc[Greeter](ore.Transient, func(ctx context.Context) (Greeter, context.Context) {
    return &FormalGreeter{}, ctx
}, "formal")

// Resolve by key
friendly, ctx := ore.GetKeyed[Greeter](ctx, "friendly")
fmt.Println(friendly.Greet()) // Hey! Great to see you!

formal, ctx := ore.GetKeyed[Greeter](ctx, "formal")
fmt.Println(formal.Greet()) // Good day. How may I assist you?

Get all implementations under a key:

greeters, ctx := ore.GetKeyedList[Greeter](ctx, "friendly")

Common use cases for keyed services:

  • Multiple payment providers ("stripe", "paypal")
  • Multiple notification channels ("email", "sms", "push")
  • Module-specific service overrides
  • Feature flags driving different implementations at runtime

Aliases

Aliases let you link a concrete type (struct pointer) to an interface, without registering the interface directly. This is powerful when you want to resolve by a broad interface but your implementations are registered under their concrete types.

type IPerson interface {
    GetName() string
}

type Broker struct{ Name string }
func (b *Broker) GetName() string { return b.Name }

type Trader struct{ Name string }
func (t *Trader) GetName() string { return t.Name }

// Register concrete types
ore.RegisterFunc[*Broker](ore.Scoped, func(ctx context.Context) (*Broker, context.Context) {
    return &Broker{Name: "Alice"}, ctx
})
ore.RegisterFunc[*Trader](ore.Scoped, func(ctx context.Context) (*Trader, context.Context) {
    return &Trader{Name: "Bob"}, ctx
})

// Link them to IPerson
ore.RegisterAlias[IPerson, *Broker]()
ore.RegisterAlias[IPerson, *Trader]() // last-linked takes precedence for Get

// Resolve — returns Trader (last linked)
person, ctx := ore.Get[IPerson](ctx)
fmt.Println(person.GetName()) // Bob

// Resolve all — returns both
people, ctx := ore.GetList[IPerson](ctx)
fmt.Println(len(people)) // 2

Precedence rules:

  • Get returns the most recently linked alias, unless a direct resolver for the interface exists — then the direct resolver always wins.
  • GetList returns all linked implementations plus any direct resolvers.
  • Alias-of-alias is not supported and will panic.
  • Ore validates at registration that the concrete type actually implements the interface.

Placeholder Services

Placeholders let you declare a dependency that doesn't exist at registration time but will be provided at runtime. This is the right pattern for request-scoped values like authenticated users, tenant IDs, or feature flag evaluations.

How it works
  1. Declare a placeholder at startup — this tells Ore "something of this type will be provided later."
  2. Provide the value at request time by injecting it into the context.
  3. Resolve it normally — any service that depends on the placeholder resolves correctly once the value is set.
// 1. At startup: declare that a *User will be injected per-request
ore.RegisterPlaceholder[*User]()

// 2. Register a service that depends on *User
ore.RegisterFunc[UserDashboard](ore.Scoped, func(ctx context.Context) (UserDashboard, context.Context) {
    user, ctx := ore.Get[*User](ctx) // depends on the placeholder
    return &userDashboardImpl{user: user}, ctx
})

// 3. At request time: inject the actual user
func handleRequest(w http.ResponseWriter, r *http.Request) {
    user := getUserFromJWT(r)
    ctx := ore.ProvideScopedValue[*User](r.Context(), user)

    dashboard, ctx := ore.Get[UserDashboard](ctx)
    // dashboard.user is set correctly
}

What happens if you forget to provide the value? Ore panics when trying to resolve a service that depends on an unfulfilled placeholder. This is intentional — a silent nil would be far worse.

Keyed placeholders follow the same pattern using RegisterKeyedPlaceholder and ProvideKeyedScopedValue.

Placeholders and real resolvers coexist:

If you later register a real resolver for the same type+key, it takes precedence over the placeholder for Get. Both appear in GetList. This lets you gradually migrate from runtime injection to proper construction logic.


Isolated Containers

By default, all registrations go into Ore's default container. For larger applications, you can create isolated containers — each with its own independent dependency graph.

This is the foundation for modular monolith architecture: each module owns its container, preventing accidental cross-module coupling.

// Create isolated containers per module
brokerContainer := ore.NewContainer()
traderContainer := ore.NewContainer()

// Register into specific containers
ore.RegisterFuncToContainer(brokerContainer, ore.Singleton, func(ctx context.Context) (BrokerService, context.Context) {
    return &brokerServiceImpl{}, ctx
})

ore.RegisterFuncToContainer(traderContainer, ore.Scoped, func(ctx context.Context) (TraderService, context.Context) {
    return &traderServiceImpl{}, ctx
})

// Resolve from specific containers
broker, ctx := ore.GetFromContainer[BrokerService](brokerContainer, ctx)
trader, ctx := ore.GetFromContainer[TraderService](traderContainer, ctx)

Services registered in one container are completely invisible to another. There is no accidental leakage between modules.

Using containers in tests:

Isolated containers are excellent for unit tests. Each test gets its own clean container with mocked dependencies — no shared global state, no test order dependencies.

func TestUserService(t *testing.T) {
    c := ore.NewContainer()
    ore.RegisterSingletonToContainer[DB](c, &mockDB{})
    ore.RegisterFuncToContainer(c, ore.Scoped, func(ctx context.Context) (UserService, context.Context) {
        db, ctx := ore.GetFromContainer[DB](c, ctx)
        return &userServiceImpl{db: db}, ctx
    })

    svc, _ := ore.GetFromContainer[UserService](c, context.Background())
    // test svc with mockDB
}

All container-scoped registration functions follow the naming convention XxxToContainer (e.g., RegisterFuncToContainer, RegisterSingletonToContainer, RegisterPlaceholderToContainer).


Validation

Ore validates your dependency graph to catch bugs at startup rather than in production.

What Ore validates

Missing dependencies — a service depends on a type that was never registered.

Circular dependencies — Service A depends on B, B depends on A. Would cause infinite recursion.

Lifetime misalignment — a long-lived service depends on a shorter-lived one. For example, a Singleton depending on a Scoped service is a bug: the Singleton gets created once, captures a scoped instance, and that instance outlives its intended scope. Ore catches this.

How to use validation

By default, Ore validates on every Get call. For production, this per-call overhead can be eliminated by disabling it after a one-time startup check:

func main() {
    // Register all services...
    ore.RegisterSingleton[DB](&dbImpl{})
    ore.RegisterFunc[UserService](ore.Scoped, NewUserService)
    // ...

    // Lock the container — no new registrations allowed after this
    ore.Seal()

    // Validate the full graph once at startup
    // This resolves everything, checks all dependencies, then clears instances
    ore.Validate()

    // Disable per-call validation in production for best performance
    ore.DisableValidation = true

    // Start your server...
}

ore.Seal() causes Ore to panic if any code tries to register a new service after the fact — useful for preventing accidental late registrations in large codebases.

ore.Validate() tries to resolve all registered services (including their full dependency chains), verifies correctness, then clears the instances so the app starts fresh.

Constructor purity matters. Since Validate() actually runs your constructors, they should be deterministic and side-effect-free. Don't make network calls, open files, or start goroutines inside constructors.


Graceful Termination

When your application shuts down, resources held by Singleton services (DB connections, open files, background workers) need to be cleaned up. Ore helps coordinate this without prescribing a specific interface.

Application shutdown (Singletons)

Define whatever cleanup interface fits your app:

type Shutdowner interface {
    Shutdown() error
}

type Closer interface {
    Close() error
}

At shutdown, ask Ore for all resolved Singletons that implement your interface. Ore returns them in reverse resolution order — dependencies are cleaned up before their dependents.

// In your shutdown handler
shutdownables := ore.GetResolvedSingletons[Shutdowner]()
for _, s := range shutdownables {
    if err := s.Shutdown(); err != nil {
        log.Printf("shutdown error: %v", err)
    }
}

Only Singletons that were actually resolved during the app's lifetime are returned. Lazily registered singletons that were never used are excluded.

Request/context shutdown (Scoped)

For scoped services, cleanup happens when the context ends:

type Disposer interface {
    Dispose(ctx context.Context) error
}

// In your request handler or middleware
ctx, cancel := context.WithCancel(r.Context())
defer func() {
    cancel()
    // Clean up scoped instances that implemented Disposer
    disposables := ore.GetResolvedScopedInstances[Disposer](ctx)
    for _, d := range disposables {
        _ = d.Dispose(ctx)
    }
}()

// Handle request using ctx...

Full example showing both patterns together:

func main() {
    ore.RegisterSingleton[*GlobalRepo](&GlobalRepo{})
    ore.RegisterCreator(ore.Scoped, &ScopedRepo{})

    ore.Seal()
    ore.Validate()

    // Simulate a request
    ctx, cancel := context.WithCancel(context.Background())
    _, ctx = ore.Get[*ScopedRepo](ctx)

    // End request — clean up scoped resources
    cancel()
    disposables := ore.GetResolvedScopedInstances[Disposer](ctx)
    for _, d := range disposables {
        _ = d.Dispose(ctx)
    }

    // Shut down app — clean up singletons
    shutdownables := ore.GetResolvedSingletons[Shutdowner]()
    for _, s := range shutdownables {
        _ = s.Shutdown()
    }
}

Here is the battle-tested pattern for setting up Ore in a production Go application:

package main

import (
    "context"
    "github.com/firasdarwish/ore"
)

func registerServices() {
    // Eager singletons for critical infrastructure
    ore.RegisterSingleton[Config](loadConfig())

    // Lazy singletons for app-wide services
    ore.RegisterFunc[DB](ore.Singleton, func(ctx context.Context) (DB, context.Context) {
        cfg, ctx := ore.Get[Config](ctx)
        return connectDB(cfg.DatabaseURL), ctx
    })

    // Scoped services for per-request work
    ore.RegisterFunc[UserRepository](ore.Scoped, func(ctx context.Context) (UserRepository, context.Context) {
        db, ctx := ore.Get[DB](ctx)
        return &userRepo{db: db}, ctx
    })

    ore.RegisterFunc[UserService](ore.Scoped, func(ctx context.Context) (UserService, context.Context) {
        repo, ctx := ore.Get[UserRepository](ctx)
        return &userService{repo: repo}, ctx
    })

    // Placeholders for runtime-injected values
    ore.RegisterPlaceholder[*AuthUser]()
}

func main() {
    registerServices()

    // Lock and validate at startup
    ore.Seal()
    ore.Validate()
    ore.DisableValidation = true // skip per-call overhead in prod

    startServer()
}

Real-World Usage Patterns

HTTP middleware injecting auth user
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, err := parseJWT(r.Header.Get("Authorization"))
        if err != nil {
            http.Error(w, "unauthorized", 401)
            return
        }
        // Inject the authenticated user into the request context
        ctx := ore.ProvideScopedValue[*AuthUser](r.Context(), user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func handleGetProfile(w http.ResponseWriter, r *http.Request) {
    svc, _ := ore.Get[UserService](r.Context())
    // UserService was built with the *AuthUser from this request's context
    profile := svc.GetCurrentUserProfile()
    json.NewEncoder(w).Encode(profile)
}
Modular monolith with isolated containers
// broker/module.go
var BrokerContainer = ore.NewContainer()

func init() {
    ore.RegisterFuncToContainer(BrokerContainer, ore.Singleton, NewBrokerConfig)
    ore.RegisterFuncToContainer(BrokerContainer, ore.Scoped, NewOrderService)
    ore.RegisterFuncToContainer(BrokerContainer, ore.Scoped, NewPositionService)
}

// trader/module.go
var TraderContainer = ore.NewContainer()

func init() {
    ore.RegisterFuncToContainer(TraderContainer, ore.Singleton, NewTraderConfig)
    ore.RegisterFuncToContainer(TraderContainer, ore.Scoped, NewTradeExecutor)
}

// main.go
func main() {
    BrokerContainer.Seal()
    TraderContainer.Seal()
    BrokerContainer.Validate()
    TraderContainer.Validate()

    // Each module resolves from its own container
    // No accidental cross-module dependency is possible
}
Plugin-style multi-implementation with GetList
type Validator interface {
    Validate(input Input) error
}

// Register multiple validators
ore.RegisterFunc[Validator](ore.Singleton, NewEmailValidator)
ore.RegisterFunc[Validator](ore.Singleton, NewPhoneValidator)
ore.RegisterFunc[Validator](ore.Singleton, NewAgeValidator)

// Run all validators
validators, _ := ore.GetList[Validator](context.Background())
for _, v := range validators {
    if err := v.Validate(input); err != nil {
        return err
    }
}
Switching implementations with keyed services
type PaymentProvider interface {
    Charge(amount float64, card string) error
}

ore.RegisterKeyedFunc[PaymentProvider](ore.Singleton, NewStripeProvider, "stripe")
ore.RegisterKeyedFunc[PaymentProvider](ore.Singleton, NewPayPalProvider, "paypal")

// Choose at runtime based on user preference
func processPayment(ctx context.Context, method string, amount float64, card string) error {
    provider, ctx := ore.GetKeyed[PaymentProvider](ctx, method)
    return provider.Charge(amount, card)
}

API Reference

Registration
Function Description
RegisterSingleton[T](impl T) Eager singleton — instance provided directly
RegisterFunc[T](lifetime, fn) Lazy registration via anonymous constructor function
RegisterCreator[T](lifetime, creator) Lazy registration via Creator[T] interface
RegisterPlaceholder[T]() Declare a future runtime-injected value
RegisterAlias[TInterface, TConcrete]() Link a concrete type to an interface
RegisterKeyedFunc[T](lifetime, fn, key) Keyed variant of RegisterFunc
RegisterKeyedSingleton[T](impl, key) Keyed eager singleton
RegisterKeyedCreator[T](lifetime, creator, key) Keyed variant of RegisterCreator
RegisterKeyedPlaceholder[T](key) Keyed placeholder

All registration functions have a ToContainer variant (e.g., RegisterFuncToContainer) for isolated containers.

Resolution
Function Description
Get[T](ctx) Resolve a single service
GetList[T](ctx) Resolve all registered implementations of T
GetKeyed[T](ctx, key) Resolve a single keyed service
GetKeyedList[T](ctx, key) Resolve all keyed implementations
GetFromContainer[T](container, ctx) Resolve from a specific container
GetListFromContainer[T](container, ctx) Resolve all from a specific container
Runtime Injection
Function Description
ProvideScopedValue[T](ctx, value) Inject a value into a placeholder via context
ProvideKeyedScopedValue[T](ctx, value, key) Keyed variant of ProvideScopedValue
ProvideScopedValueToContainer(container, ctx, value) Inject into a specific container
Lifecycle
Function Description
Seal() Lock the default container — no further registrations
Validate() Validate the full dependency graph of the default container
GetResolvedSingletons[T]() Get all resolved singletons implementing T (for shutdown)
GetResolvedScopedInstances[T](ctx) Get all resolved scoped instances implementing T (for disposal)
DisableValidation = true Disable per-call validation (use after startup Validate())
Container
Function Description
NewContainer() Create a new isolated container
container.Seal() Lock an isolated container
container.Validate() Validate an isolated container's dependency graph
container.DisableValidation Per-container validation toggle

Full documentation and examples: ore.lilury.com

GitHub: github.com/firasdarwish/ore

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultContainer = NewContainer()
)

Functions

func ContainerID added in v0.5.1

func ContainerID() int32

func Get

func Get[T any](ctx context.Context) (T, context.Context)

Get Retrieves an instance based on type and key (panics if no valid implementations)

func GetFromContainer added in v0.5.0

func GetFromContainer[T any](con *Container, ctx context.Context) (T, context.Context)

GetFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations)

func GetKeyed added in v0.6.0

func GetKeyed[T any, K comparable](ctx context.Context, key K) (T, context.Context)

GetKeyed Retrieves an instance based on type and key (panics if no valid implementations)

func GetKeyedFromContainer added in v0.6.0

func GetKeyedFromContainer[T any, K comparable](con *Container, ctx context.Context, key K) (T, context.Context)

GetKeyedFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations)

func GetKeyedList added in v0.6.0

func GetKeyedList[T any, K comparable](ctx context.Context, key K) ([]T, context.Context)

GetKeyedList Retrieves a list of instances based on type and key

func GetKeyedListFromContainer added in v0.6.0

func GetKeyedListFromContainer[T any, K comparable](con *Container, ctx context.Context, key K) ([]T, context.Context)

GetKeyedListFromContainer Retrieves a list of instances from the given container based on type and key

func GetList

func GetList[T any](ctx context.Context) ([]T, context.Context)

GetList Retrieves a list of instances based on type and key

func GetListFromContainer added in v0.5.0

func GetListFromContainer[T any](con *Container, ctx context.Context) ([]T, context.Context)

GetListFromContainer Retrieves a list of instances from the given container based on type and key

func GetResolvedScopedInstances added in v0.3.0

func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterface

GetResolvedScopedInstances retrieves a list of Scoped instances that implement the [TInterface]. The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one. If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. This function is useful for cleaning operations.

Example:

     disposableInstances := ore.GetResolvedScopedInstances[Disposer](ctx)
	 for _, disposable := range disposableInstances {
	   disposable.Dispose()
	 }

func GetResolvedSingletons added in v0.3.0

func GetResolvedSingletons[TInterface any]() []TInterface

GetResolvedSingletons retrieves a list of Singleton instances that implement the [TInterface]. The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the "most recently" created one. If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. This function is useful for cleaning operations.

Example:

     disposableSingletons := ore.GetResolvedSingletons[Disposer]()
	 for _, disposable := range disposableSingletons {
	   disposable.Dispose()
	 }

func GetResolvedSingletonsFromContainer added in v0.5.0

func GetResolvedSingletonsFromContainer[TInterface any](con *Container) []TInterface

GetResolvedSingletonsFromContainer retrieves a list of Singleton instances that implement the [TInterface] from the given container. See GetResolvedSingletons for more information.

func IsSealed added in v0.5.1

func IsSealed() bool

IsSealed checks whether the DEFAULT container is sealed (in readonly mode)

func Name added in v0.5.1

func Name() string

func ProvideKeyedScopedValue added in v0.6.0

func ProvideKeyedScopedValue[T any, K comparable](ctx context.Context, value T, key K) context.Context

ProvideKeyedScopedValue injects a concrete value into the given context. This value will be available only to the default container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholder function for more info.

func ProvideKeyedScopedValueToContainer added in v0.6.0

func ProvideKeyedScopedValueToContainer[T any, K comparable](con *Container, ctx context.Context, value T, key K) context.Context

ProvideKeyedScopedValueToContainer injects a concrete value into the given context. This value will be available only to the given container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholderToContainer function for more info.

func ProvideScopedValue added in v0.5.0

func ProvideScopedValue[T any](ctx context.Context, value T) context.Context

ProvideScopedValue injects a concrete value into the given context. This value will be available only to the default container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholder function for more info.

func ProvideScopedValueToContainer added in v0.5.0

func ProvideScopedValueToContainer[T any](con *Container, ctx context.Context, value T) context.Context

ProvideScopedValueToContainer injects a concrete value into the given context. This value will be available only to the given container. And the container can only resolve this value if it has the matching (type and key's) Placeholder registered. Checkout the RegisterPlaceholderToContainer function for more info.

func RegisterAlias added in v0.3.0

func RegisterAlias[TInterface, TImpl any]()

RegisterAlias Registers an interface type to a concrete implementation. Allowing you to register the concrete implementation to the default container and later get the interface from it.

func RegisterAliasToContainer added in v0.5.0

func RegisterAliasToContainer[TInterface, TImpl any](con *Container)

RegisterAliasToContainer Registers an interface type to a concrete implementation in the given container. Allowing you to register the concrete implementation to the container and later get the interface from it.

func RegisterCreator added in v0.6.0

func RegisterCreator[T any](lifetime Lifetime, creator Creator[T])

RegisterCreator Registers a lazily initialized value using a `Creator[T]` interface

func RegisterCreatorToContainer added in v0.6.0

func RegisterCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T])

RegisterCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface

func RegisterFunc added in v0.6.0

func RegisterFunc[T any](lifetime Lifetime, initializer Initializer[T])

RegisterFunc Registers a lazily initialized value using an `Initializer[T]` function signature

func RegisterFuncToContainer added in v0.6.0

func RegisterFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T])

RegisterFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature

func RegisterKeyedCreator added in v0.6.0

func RegisterKeyedCreator[T any, K comparable](lifetime Lifetime, creator Creator[T], key K)

RegisterKeyedCreator Registers a lazily initialized value using a `Creator[T]` interface

func RegisterKeyedCreatorToContainer added in v0.6.0

func RegisterKeyedCreatorToContainer[T any, K comparable](con *Container, lifetime Lifetime, creator Creator[T], key K)

RegisterKeyedCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface

func RegisterKeyedFunc added in v0.6.0

func RegisterKeyedFunc[T any, K comparable](lifetime Lifetime, initializer Initializer[T], key K)

RegisterKeyedFunc Registers a lazily initialized value using an `Initializer[T]` function signature

func RegisterKeyedFuncToContainer added in v0.6.0

func RegisterKeyedFuncToContainer[T any, K comparable](con *Container, lifetime Lifetime, initializer Initializer[T], key K)

RegisterKeyedFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature

func RegisterKeyedPlaceholder added in v0.6.0

func RegisterKeyedPlaceholder[T any, K comparable](key K)

RegisterKeyedPlaceholder registers a future value with Scoped lifetime. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterKeyedPlaceholderToContainer added in v0.6.0

func RegisterKeyedPlaceholderToContainer[T any, K comparable](con *Container, key K)

RegisterKeyedPlaceholderToContainer registers a future value with Scoped lifetime to the given container. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterKeyedSingleton added in v0.6.0

func RegisterKeyedSingleton[T any, K comparable](impl T, key K)

RegisterKeyedSingleton Registers an eagerly instantiated singleton value To register an eagerly instantiated scoped value use ProvideScopedValue

func RegisterKeyedSingletonToContainer added in v0.6.0

func RegisterKeyedSingletonToContainer[T any, K comparable](con *Container, impl T, key K)

RegisterKeyedSingletonToContainer Registers an eagerly instantiated singleton value to the given container. To register an eagerly instantiated scoped value use ProvideScopedValueToContainer

func RegisterPlaceholder added in v0.6.0

func RegisterPlaceholder[T any]()

RegisterPlaceholder registers a future value with Scoped lifetime. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterPlaceholderToContainer added in v0.6.0

func RegisterPlaceholderToContainer[T any](con *Container)

RegisterPlaceholderToContainer registers a future value with Scoped lifetime to the given container. This value will be injected in runtime using the ProvideScopedValue function. Resolving objects which depend on this value will panic if the value has not been provided. Placeholder with the same type and key can be registered only once.

func RegisterSingleton added in v0.6.0

func RegisterSingleton[T any](impl T)

RegisterSingleton Registers an eagerly instantiated singleton value To register an eagerly instantiated scoped value use ProvideScopedValue

func RegisterSingletonToContainer added in v0.6.0

func RegisterSingletonToContainer[T any](con *Container, impl T)

RegisterSingletonToContainer Registers an eagerly instantiated singleton value to the given container. To register an eagerly instantiated scoped value use ProvideScopedValueToContainer

func Seal added in v0.5.1

func Seal()

Seal puts the DEFAULT container into read-only mode, preventing any further registrations.

func Validate added in v0.4.0

func Validate()

Validate invokes all registered resolvers. It panics if any of them fails. It is recommended to call this function on application start, or in the CI/CD test pipeline The objective is to panic early when the container is bad configured. For eg:

  • (1) Missing dependency (forget to register certain resolvers)
  • (2) cyclic dependency
  • (3) lifetime misalignment (a longer lifetime service depends on a shorter one).

Types

type Container added in v0.5.0

type Container struct {
	//DisableValidation is false by default, Set to true to skip validation.
	// Use case: you called the [Validate] function (either in the test pipeline or on application startup).
	// So you are confident that your registrations are good:
	//
	//   - no missing dependencies
	//   - no circular dependencies
	//   - no lifetime misalignment (a longer lifetime service depends on a shorter one).
	//
	// You don't need Ore to validate over and over again each time it creates a new concrete.
	// It's a waste of resource especially when you will need Ore to create a million of transient concretes
	// and any "pico" seconds or memory allocation matter for you.
	//
	// In this case, you can set DisableValidation = true.
	//
	// This config would impact also the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions,
	// the returning order would be no longer guaranteed.
	DisableValidation bool
	// contains filtered or unexported fields
}

func NewContainer added in v0.5.0

func NewContainer() *Container

func (*Container) ContainerID added in v0.5.1

func (c *Container) ContainerID() int32

func (*Container) IsSealed added in v0.5.1

func (this *Container) IsSealed() bool

IsSealed checks whether the container is sealed (in readonly mode)

func (*Container) Name added in v0.5.1

func (c *Container) Name() string

func (*Container) Seal added in v0.5.1

func (this *Container) Seal()

Seal puts the container into read-only mode, preventing any further registrations.

func (*Container) SetName added in v0.5.1

func (this *Container) SetName(name string) *Container

func (*Container) Validate added in v0.5.0

func (this *Container) Validate()

Validate invokes all registered resolvers. It panics if any of them fails. It is recommended to call this function on application start, or in the CI/CD test pipeline The objective is to panic early when the container is bad configured. For eg:

  • (1) Missing dependency (forget to register certain resolvers)
  • (2) cyclic dependency
  • (3) lifetime misalignment (a longer lifetime service depends on a shorter one).

type Creator

type Creator[T any] interface {
	New(ctx context.Context) (T, context.Context)
}

type Initializer

type Initializer[T any] func(ctx context.Context) (T, context.Context)

type Lifetime added in v0.1.1

type Lifetime int
const (
	Transient Lifetime = 0
	Scoped    Lifetime = 1
	Singleton Lifetime = 2
)

The bigger the value, the longer the lifetime

func (Lifetime) String added in v0.4.0

func (this Lifetime) String() string

Directories

Path Synopsis
internal
testtools/assert2
assert2 package add missing assertions from testify/assert package
assert2 package add missing assertions from testify/assert package

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL