Skip to content

Lifecycles

When registering dependencies in Dipend, you choose how they are instantiated and managed over time. This is known as their lifecycle.

Dipend supports two lifecycles out of the box:

LifecycleInstantiated WhenInstance Reused?Suitable For
SingletonOnce (on demand or eagerly)YesConfigs, services, loggers
TransientEvery requestNoRequest-based, stateful logic
Per ContextOnce per context (on demand or eagerly)Yes (same context)Unit of work, scoped services, batching

A singleton is created once and reused throughout the entire application.

  • The class is stateless or expensive to construct.
  • You want shared behavior or shared state (e.g., configuration, database connection, loggers).
  • Instantiated once.
  • Always the same instance is returned.
TypeScript
dependencyContainer.addSingleton<ILogger, Logger>();
const logger1 = dependencyContainer.getDependency<ILogger>();
const logger2 = dependencyContainer.getDependency<ILogger>();
console.log(logger1 === logger2); // true ✅
Python
dependency_container.add_singleton(UserService)
service1 = dependency_container.get_dependency(UserService)
service2 = dependency_container.get_dependency(UserService)
print(service1 is service2) # True ✅

A transient dependency is created every time it is requested.

  • The class holds request-specific or temporary state.
  • You need complete isolation between usages.
  • A new instance is created for every getDependency call.
  • Dependencies of a transient service are resolved fresh as well.
TypeScript
dependencyContainer.addTransient<UserService>();
const service1 = dependencyContainer.getDependency<UserService>();
const service2 = dependencyContainer.getDependency<UserService>();
console.log(service1 === service2); // false ✅
Python
dependency_container.add_transient(UserService)
service1 = dependency_container.get_dependency(UserService)
service2 = dependency_container.get_dependency(UserService)
print(service1 is service2) # False ✅

A per context dependency is created once per resolution scope, perfect for scenarios where you want consistency within a request, job, or transaction, without sharing across the entire app.

  • You want all dependencies in a single operation (e.g., a request) to share the same instance.
  • You need consistency across a batch of operations but not globally.

Useful for Unit of Work, scoped caching, or transactional logic.

  • A new instance is created per resolution context.
  • That instance is reused only within that context of resolutions.
  • Outside of that context, a new instance is created.
Python
dependency_container.add_per_context(UserService)
with api.context():
service1 = dependency_container.get_dependency(UserService)
with api.context():
service2 = dependency_container.get_dependency(UserService)
service3 = dependency_container.get_dependency(UserService)
print(service2 is service3) # True ✅
print(service1 is service2) # False ✅

Dipend handles mixed dependencies gracefully. For example:

  • A Singleton can depend on other Singletons or Transients (read below).
  • A Transient can depend on Singletons or other Transients.
  • A Per Context can depend on Singletons, Transients, or other Per Context dependencies within the same context.

However:

⚠️ A Singleton depending on a Transient will resolve the transient only once — when the singleton is created.

This is expected behavior, but it’s important to be aware if you’re injecting anything stateful into a singleton.

⚠️ A Singleton depending on a Per Context will not receive context-scoped behavior. The dependency will be resolved once, outside of any context.

To preserve correct scoping, avoid injecting context-bound services (like Per Context) into singletons unless you’re managing resolution manually within the context.