Forge Integration

How Forge exposes Vessel for app services and extension wiring

Forge uses Vessel as its internal dependency injection engine. Every Forge application creates a Vessel container at startup and exposes it via app.Container(). Forge also re-exports all Vessel functions under the forge package for convenience.

Accessing the Container

import "github.com/xraph/forge"

app := forge.New(
    forge.WithAppName("my-app"),
    forge.WithAppVersion("1.0.0"),
)

// The container is a vessel.Vessel instance
c := app.Container()

forge.Container is a type alias for vessel.Vessel, so all Vessel functions work directly with the value returned by app.Container().

Core Services Registered by Forge

When a Forge app is created, these services are automatically registered in the container -- both by name (for backward compatibility) and by type (for constructor injection):

ServiceTypeName Key
Loggerforge.Logger"logger"
Config Managerforge.ConfigManager"forge.config"
Metricsforge.Metrics"metrics"
Health Managerforge.HealthManager"forge.health"
Routerforge.Router"forge.router"

Resolving Core Services

By type (recommended):

import "github.com/xraph/vessel"

logger, err := vessel.InjectType[forge.Logger](app.Container())
metrics, err := vessel.InjectType[forge.Metrics](app.Container())

By name:

logger, err := vessel.Resolve[forge.Logger](app.Container(), "logger")

Forge also provides convenience helpers:

logger, err := forge.GetLogger(app.Container())
metrics, err := forge.GetMetrics(app.Container())
healthMgr, err := forge.GetHealthManager(app.Container())

Forge Re-exports

Forge re-exports all Vessel DI functions under the forge package. This means you can use either vessel.* or forge.* -- they call the same underlying functions.

// These are equivalent:
vessel.ProvideConstructor(c, NewService)
forge.ProvideConstructor(c, NewService)

// These are equivalent:
vessel.InjectType[*Service](c)
forge.InjectType[*Service](c)

// These are equivalent:
vessel.RegisterSingleton[*DB](c, "db", factory)
forge.RegisterSingleton[*DB](c, "db", factory)

Forge also provides type aliases for Vessel wrapper types:

Forge aliasVessel type
forge.LazyRef[T]vessel.Lazy[T]
forge.OptionalLazyRef[T]vessel.OptionalLazy[T]
forge.ProviderRef[T]vessel.Provider[T]
forge.LazyAnyvessel.LazyAny
forge.OptionalLazyAnyvessel.OptionalLazyAny

Writing Extensions

Extensions register their services during the Register phase using the container from app.Container().

import (
    "context"
    "github.com/xraph/forge"
    "github.com/xraph/vessel"
)

type MyExtension struct {
    app forge.App
}

func (e *MyExtension) Register(app forge.App) error {
    e.app = app
    c := app.Container()

    // Register services using vessel (or forge -- equivalent)
    vessel.ProvideConstructor(c, NewMyService)

    return nil
}

func (e *MyExtension) Start(ctx context.Context) error {
    svc, err := vessel.InjectType[*MyService](e.app.Container())
    if err != nil {
        return err
    }
    return svc.Start(ctx)
}

func (e *MyExtension) Stop(ctx context.Context) error {
    return nil
}

Depending on Other Extensions

Use constructor injection to depend on services from other extensions. Because constructors are resolved by type, there is no need to coordinate string keys across extensions.

// This extension depends on the database extension's DatabaseManager
func NewMyService(manager *database.DatabaseManager, logger forge.Logger) (*MyService, error) {
    db, err := manager.SQL("primary")
    if err != nil {
        return nil, err
    }
    return &MyService{db: db, logger: logger}, nil
}

func (e *MyExtension) Register(app forge.App) error {
    return vessel.ProvideConstructor(app.Container(), NewMyService)
}

Resolving Services During Registration

If you need a service to be fully started before using it during the Register phase, use ResolveReady:

func (e *MyExtension) Register(app forge.App) error {
    ctx := context.Background()

    dbManager, err := vessel.ResolveReady[*database.DatabaseManager](
        ctx, app.Container(), "database-manager",
    )
    if err != nil {
        return fmt.Errorf("database required: %w", err)
    }

    // dbManager is now fully started with open connections
    e.redis, _ = dbManager.Redis("cache")
    return nil
}

Interface Resolution Across Extensions

Use interface types for loose coupling between extensions:

// Extension A registers a concrete implementation
vessel.ProvideConstructor(app.Container(), func(svc *EventService) (core.EventBus, error) {
    return svc, nil
})

// Extension B resolves by interface type
eventBus, err := vessel.InjectType[core.EventBus](app.Container())

Which Package to Import

ScenarioRecommendation
Standalone library (no Forge)import "github.com/xraph/vessel"
Forge application codeEither works; forge is convenient for type aliases
Forge extensionvessel for registration, forge for types like forge.Logger
Shared package used in bothimport "github.com/xraph/vessel"

How is this guide?

On this page