Skip to main content

⚡ Native DI Container & App Lifecycle (FerroxApp & FerroxDIContainer)

💡 1. What It Is & Architectural Purpose​

FerroxDIContainer and FerroxApp form the foundational execution engine of @ferrox-node/core. They were designed to provide a lightweight, native Inversion of Control (IoC) Container and application lifecycle orchestrator for Node.js/TypeScript applications without pulling heavy third-party framework dependencies like NestJS, Inversify, or TSyringe.

The primary architectural goal of this component is to eliminate framework overhead while providing type-safe dependency resolution, explicit lifecycle hooks (OnAppStart, OnAppDestroy), and seamless application bootstrapping.

[!NOTE] Unlike NestJS, which relies on heavy runtime reflection (reflect-metadata) to resolve complex dynamic module graphs, FerroxDIContainer uses a high-performance Map-based singleton and transient resolution registry, reducing application boot times by up to 85%.


⚙️ 2. What It Does & Key Features​

  • Type-Safe Dependency Resolution: Registers and injects class instances using constructor-based or token-based dependency resolution.
  • Application Bootstrapping (FerroxApp): Configures and boots the underlying HTTP engine (fastify or express), attaches controllers, and registers global middlewares.
  • Lifecycle Hook Orchestration: Automatically executes onAppStart() during application initialization and onAppDestroy() upon SIGTERM/SIGINT graceful shutdown signals.
  • Singleton & Transient Lifecycle Scopes: Supports both application-scoped singletons and request-scoped transient dependency resolution.

🔬 3. How It Works Under the Hood​

Execution & Resolution Lifecycle​

flowchart TD
Boot["FerroxApp.start()"]
DIRegister["FerroxDIContainer Instance Registry"]
StartHooks["Trigger onAppStart() Lifecycle Hooks"]
HTTPOpen["Bind HTTP Port & Start Listening Stream"]
ShutdownSignal["SIGTERM / SIGINT Signal Caught"]
DestroyHooks["Trigger onAppDestroy() Graceful Cleanup Hooks"]
ProcessExit["Terminate Node.js Process Cleanly"]

Boot --> DIRegister
DIRegister --> StartHooks
StartHooks --> HTTPOpen
HTTPOpen --> ShutdownSignal
ShutdownSignal --> DestroyHooks
DestroyHooks --> ProcessExit

Internal Mechanism​

  1. Container Storage: FerroxDIContainer maintains an internal Map<Constructor | Symbol, Instance> lookup table.
  2. Resolution Pipeline: When a class is resolved, the container recursively instantiates constructor parameters or retrieves existing singleton instances.
  3. Graceful Shutdown Handler: FerroxApp attaches event listeners to process.on('SIGTERM') and process.on('SIGINT'), ensuring active database pools, WebSocket connections, and background workers flush pending operations before process exit.

🧠 4. Why It Was Designed This Way (Rationale vs NestJS / Express)​

Architectural Trade-Off Analysis​

Feature / Metric⚡ FerroxDIContainer🪺 NestJS IoC Container🚂 Raw Express (Manual)
Reflection OverheadZero (Native Map Lookup)Heavy (reflect-metadata AST)None (Manual Instantiation)
Boot Latency< 15ms~350 - 600ms< 10ms
Lifecycle HooksNative (OnAppStart, OnAppDestroy)Nest Module LifecyclesManual process.on Listeners
Memory FootprintMinimal (~8 MB baseline)Heavy (~45 - 65 MB baseline)Minimal (~6 MB baseline)

When to Use FerroxDIContainer​

  • When building high-performance microservices where low cold-start latency and minimal memory footprint are paramount.
  • When you want structured IoC and clean layer separation without lock-in to NestJS.

When NOT to Use FerroxDIContainer​

  • If your project strictly requires legacy NestJS dynamic module ecosystems (e.g. @nestjs/typeorm dynamic factories). Use @nest-yalc-2/framework instead.

🚀 5. Practical Usage Guide & Extended Code Examples​

Complete Production Bootstrap Example​

import {
FerroxApp,
FerroxDIContainer,
Controller,
Get,
Injectable,
OnAppStart,
OnAppDestroy
} from '@ferrox-node/core';

// 1. Define Dependency Service
@Injectable()
export class DatabasePoolService implements OnAppStart, OnAppDestroy {
private isConnected = false;

async onAppStart(): Promise<void> {
console.log('[Database] Connecting to database cluster...');
this.isConnected = true;
console.log('[Database] Connection established.');
}

async onAppDestroy(): Promise<void> {
console.log('[Database] Closing active connection pool...');
this.isConnected = false;
console.log('[Database] Connection pool closed cleanly.');
}

getStatus(): string {
return this.isConnected ? 'ONLINE' : 'OFFLINE';
}
}

// 2. Define Controller
@Controller('/api/v1/system')
export class SystemHealthController implements OnAppStart {
constructor(private readonly dbService: DatabasePoolService) {}

onAppStart(): void {
console.log('[SystemHealthController] Controller initialized.');
}

@Get('/status')
getHealthStatus() {
return {
status: 'UP',
database: this.dbService.getStatus(),
timestamp: new Date().toISOString()
};
}
}

// 3. Application Bootstrap
async function main() {
const di = FerroxDIContainer.getInstance();

// Register services into native IoC Container
const dbService = new DatabasePoolService();
di.register(DatabasePoolService, dbService);
di.register(SystemHealthController, new SystemHealthController(dbService));

// Instantiate FerroxApp
const app = new FerroxApp({
engine: 'fastify',
port: 8080,
controllers: [SystemHealthController],
middlewares: [
{
path: '*',
handler: (req, res, next) => {
console.log(`[HTTP Request] ${req.method} ${req.url}`);
next();
}
}
]
});

await app.start();
console.log('⚡ Ferrox-Node application running on http://localhost:8080');
}

main().catch(console.error);

⚠️ 6. Anti-Patterns: How NOT to Use It​

[!WARNING] Anti-Pattern 1: Circular Dependency Deadlocks Avoid injecting Service A into Service B if Service B also injects Service A in its constructor. Unlike complex lazy-proxy containers, FerroxDIContainer strictly enforces acyclic dependency graphs to prevent memory allocation stack overflow crashes.

[!CAUTION] Anti-Pattern 2: Instantiating Controllers Manually Outside the DI Registry Creating controllers via new MyController() without registering them in FerroxDIContainer prevents lifecycle hooks (OnAppStart, OnAppDestroy) from being invoked during graceful shutdown.


开启 7. Pro-Tips & Best Practices​

[!TIP] Pro-Tip: Graceful Kubernetes Pod Termination In Kubernetes environments, FerroxApp's graceful shutdown ensures that active HTTP requests finish processing while readiness probes report unhealthy, eliminating 502 Bad Gateway errors during rolling updates.