---
title: Lifetimes and Scopes
description: Manage singleton, transient, and scoped service lifetimes
---

Every service in Vessel has a lifecycle that determines how often instances are created and how long they live.

## Singleton

One instance for the full application lifetime. This is the default lifecycle.

```go
// Constructor-based (recommended)
vessel.Provide(c, NewDatabase, vessel.AsSingleton())

// Both calls return the same instance
db1, _ := vessel.Inject[*Database](c)
db2, _ := vessel.Inject[*Database](c)
// db1 == db2
```

With a named registration:

```go
vessel.ProvideNamed(c, "database", func() (*Database, error) {
    return NewDatabase("postgres://localhost:5432/app"), nil
}, vessel.AsSingleton())

db1, _ := vessel.InjectNamed[*Database](c, "database")
db2, _ := vessel.InjectNamed[*Database](c, "database")
// db1 == db2
```

Use singleton for connection pools, configuration, registries, and long-lived clients.

## Transient

A new instance is created every time the service is resolved.

```go
vessel.Provide(c, NewRequestLogger, vessel.AsTransient())

// Each call returns a different instance
log1, _ := vessel.Inject[*RequestLogger](c)
log2, _ := vessel.Inject[*RequestLogger](c)
// log1 != log2
```

Use transient for short-lived computation objects, request builders, or anything that should not be shared.

## Scoped

One instance per scope. A scope is typically created per HTTP request, but you can use scopes for any bounded operation.

```go
vessel.Provide(c, NewRequestContext, vessel.AsScoped())

// Create a scope (e.g., in HTTP middleware)
scope := c.BeginScope()

// Within the same scope, resolution returns the same instance
ctx1, _ := vessel.Inject[*RequestContext](scope)
ctx2, _ := vessel.Inject[*RequestContext](scope)
// ctx1 == ctx2
```

Use scoped for request-bound state, correlation data, and per-request caches.

## Value Registration

Register a pre-built instance directly using `ProvideValue`. This is always a singleton.

```go
config := &AppConfig{Port: 8080, Debug: true}
vessel.ProvideValue(c, config)

cfg, _ := vessel.Inject[*AppConfig](c)
```

To register a value with a name:

```go
vessel.ProvideValue(c, config, vessel.WithName("config"))

cfg, _ := vessel.InjectNamed[*AppConfig](c, "config")
```

## Interface Registration

Use the `As` constructor option to register a concrete type as an interface type.

```go
vessel.Provide(c, NewPostgresUserRepo, vessel.As(new(UserRepository)))

// Resolve by interface type
repo, err := vessel.Inject[UserRepository](c)
```

You can register as multiple interfaces at once:

```go
vessel.Provide(c, NewMyService, vessel.As(new(io.Reader), new(io.Writer)))

reader, _ := vessel.Inject[io.Reader](c)
writer, _ := vessel.Inject[io.Writer](c)
```

## Lifecycle Options Summary

Control the lifecycle when calling `Provide` or `ProvideNamed`:

```go
// Singleton (default)
vessel.Provide(c, NewDatabase, vessel.AsSingleton())

// Transient
vessel.Provide(c, NewRequestLogger, vessel.AsTransient())

// Scoped
vessel.Provide(c, NewRequestContext, vessel.AsScoped())
```

## Lifetime Guidance

| Lifetime | Use For | Example |
|---|---|---|
| Singleton | Shared, long-lived resources | DB pools, config, HTTP clients |
| Transient | Disposable, per-use objects | Request builders, temporary buffers |
| Scoped | Per-request or per-operation state | Request context, correlation IDs |

- Default to singleton for most services.
- Use transient only when sharing would cause correctness issues.
- Use scoped for anything tied to a request or bounded operation.
