Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifecycle Hooks Execution Order does not respect Provider Dependencies within the Same Module #14773

Open
1 task done
ori88c opened this issue Mar 9, 2025 · 2 comments
Open
1 task done
Labels
needs triage This issue has not been looked into type: enhancement 🐺

Comments

@ori88c
Copy link

ori88c commented Mar 9, 2025

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

NestJS ensures a topological instantiation order for providers based on their dependency graph (i.e., new MyProvider(...) respects dependencies). However, this ordering is not enforced for asynchronous lifecycle hooks, such as:

Describe the solution you'd like

Feature Request

Lifecycle hooks within a module should execute sequentially in a topological (for init) and reverse-topological (for teardown) order to ensure:

  • During initialization: A provider’s dependencies have fully initialized (onModuleInit resolved) before its own onModuleInit executes.
  • During teardown: A provider’s dependents have fully torn down (onModuleDestroy resolved) before its own onModuleDestroy executes.

This would improve lifecycle orchestration and prevent potential race-conditions.

Teachability, documentation, adoption, migration strategy

Ideally, the Lifecycle Hooks documentation will explicitly clarify the following points:

  • Module Initialization Order: Modules are initialized sequentially in topological order during the init phase and reverse-topological order during teardown.
  • Provider Initialization: Within each module, providers' onModuleInit hooks are invoked sequentially in topological order.
  • Provider Teardown: Within each module, providers' onModuleDestroy hooks are invoked sequentially in reverse-topological order.

This clarification will help developers better understand the precise execution flow of lifecycle hooks.

What is the motivation / use case for changing the behavior?

Comparison with Other Popular Dependency-Injection Framework

My suggestion aligns with Spring's default behavior:
https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html
"The order of startup and shutdown invocations can be important. If a "depends-on" relationship exists between any two objects, the dependent side starts after its dependency, and it stops before its dependency".

Use Case Example

Consider a module with two providers:

  1. A Kafka client provider that establishes a connection in onModuleInit().
  2. A Kafka publisher provider (built on top of the client) that creates a test topic in onModuleInit().

Issue

Since both providers belong to the same module, Nest does not guarantee a topological execution order for their lifecycle hooks. This leads to potential failures:

  • Initialization (onModuleInit): The publisher may attempt to publish before the client has established a connection.
  • Teardown (onModuleDestroy): The publisher may attempt to send messages after the client has disconnected.

Current Approach to Mitigate the Issue

Developers can implement custom lifecycle coordination layers, but this undermines NestJS’s built-in orchestration capabilities. A practical workaround is to place each provider with an asynchronous onModuleInit or onModuleDestroy hook into its own dedicated module - effectively creating one-provider modules.
However, this approach introduces significant downsides: excessive modularity and fragmentation, as logically related providers become scattered across multiple modules.

@ori88c ori88c added needs triage This issue has not been looked into type: enhancement 🐺 labels Mar 9, 2025
@kamilmysliwiec
Copy link
Member

kamilmysliwiec commented Mar 18, 2025

As the name of the lifecycle hook suggests, it triggers on the module initialization meaning provider dependencies are not taken into account. We'd need to add a new hook if we wanted to introduce such a feature.

@ori88c
Copy link
Author

ori88c commented Mar 18, 2025

As the name of the lifecycle hook suggests, it triggers on the module initialization meaning provider dependencies are not taken into account. We'd need to add a new hook if we wanted to introduce such a feature.

Terminology-wise, the term "module init" does not inherently convey an initialization policy - whether concurrent or sequential. Just as "task scheduling" does not specify whether it refers to recurring or one-time execution.
The decision to apply a sequential policy for module initialization while using a concurrent policy for providers within a module is an implementation detail.
Regardless of the outcome of this feature request, I believe this implementation detail ("meaning provider dependencies are not taken into account") is worth highlighting in the documentation:
https://docs.nestjs.com/fundamentals/lifecycle-events

Introducing additional hooks, such as onProviderInit, would not necessarily clarify execution order either, as the name alone would not indicate whether providers are initialized sequentially.

A flexible and intuitive approach could be to introduce an optional argument to app.init() and/or app.listen() (or extend an existing option) that allows users to define their desired execution policy. For example:

interface IModuleOptions {
  // ...
  moduleHooksExecutionPolicy: 'sequential' | 'concurrent';
}

This approach keeps the API clean and eliminates the need for additional lifecycle hooks. Would this be a reasonable consideration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage This issue has not been looked into type: enhancement 🐺
Projects
None yet
Development

No branches or pull requests

2 participants