You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This RFC proposes refactoring NexusDI to be async-first, meaning all container operations (set, get, resolve) return Promises. While this introduces the need for await keywords, it unlocks significant architectural benefits that make the library more powerful, modern, and future-proof.
Motivation
Current Limitations
NexusDI's current synchronous architecture creates several constraints:
No Async Factory Support: Factories that need to perform async operations (database connections, API calls, file I/O) are not supported
No Async Resource Management: Cannot properly handle resources that require async initialization or cleanup
No Dynamic Module Loading: Cannot load modules from external sources or perform async configuration
Limited Initialization Patterns: Cannot support complex initialization sequences that modern applications require
Real-World Use Cases That Are Currently Impossible
// ❌ IMPOSSIBLE with current sync architectureawaitcontainer.set({token: DATABASE_CONNECTION,useFactory: async()=>{constdb=newDatabase();awaitdb.connect();awaitdb.migrate();returndb;},});// ❌ IMPOSSIBLE: Async configuration loadingawaitcontainer.set({token: CONFIG_SERVICE,useFactory: async()=>{constresponse=awaitfetch('/api/config');returnawaitresponse.json();},});
Detailed Design
Core API Changes
Before (Sync)
// Current sync APIconstcontainer=newNexus();container.set(MyService);// voidcontainer.set(TOKEN,{useValue: value});// voidconstservice=container.get(MyService);// Tconstinstance=container.resolve(Class);// T
After (Async-First)
// New async-first APIconstcontainer=newNexus();awaitcontainer.set(MyService);// Promise<this>awaitcontainer.set(TOKEN,{useValue: value});// Promise<this>constservice=awaitcontainer.get(MyService);// Promise<T>constinstance=awaitcontainer.resolve(Class);// Promise<T>// Parallel registration for performanceawaitcontainer.setMany(ServiceA,ServiceB,ServiceC);
New Capabilities Unlocked
1. Async Factories
// Database connection with async initializationawaitcontainer.set({token: DATABASE_CONNECTION,useFactory: async()=>{constdb=newDatabase(process.env.DATABASE_URL);awaitdb.connect();awaitdb.runMigrations();returndb;},});// Configuration loaded from external APIawaitcontainer.set({token: APP_CONFIG,useFactory: async()=>{constresponse=awaitfetch('/api/config');if(!response.ok){thrownewError('Failed to load configuration');}returnawaitresponse.json();},});// Service that depends on async-initialized resourcesawaitcontainer.set({token: USER_SERVICE,useFactory: async(db: Database,config: AppConfig)=>{constcache=newRedisCache(config.redis);awaitcache.connect();returnnewUserService(db,cache);},deps: [DATABASE_CONNECTION,APP_CONFIG],});
2. Dynamic Module Loading
// Load modules from external sourcesconstremoteModule=awaitimport('./modules/FeatureModule.js');awaitcontainer.set(remoteModule.FeatureModule);// Conditional module loading based on environmentif(process.env.NODE_ENV==='development'){constdevModule=awaitimport('./dev/DevToolsModule.js');awaitcontainer.set(devModule.DevToolsModule);}
3. Micro-Frontend Architecture Support
// Load micro-frontend modules dynamicallyclassMicroFrontendLoader{constructor(privatecontainer: Nexus){}asyncloadMicroFrontend(name: string,remoteUrl: string){// Load remote module federation entryconstremoteContainer=awaitimport(/* webpackIgnore: true */remoteUrl);// Get the micro-frontend's DI moduleconstmicroFrontendModule=awaitremoteContainer.get('./Module');// Register the entire micro-frontend's servicesawaitthis.container.set(microFrontendModule.default);// Load micro-frontend's configurationconstconfig=awaitremoteContainer.get('./config');awaitthis.container.set({token: `${name}_CONFIG`,useValue: config.default,});}}// Usage in shell applicationconstloader=newMicroFrontendLoader(container);// Dynamically load micro-frontends based on user permissionsconstuserPermissions=awaitauthService.getPermissions();if(userPermissions.includes('analytics')){awaitloader.loadMicroFrontend('analytics','http://analytics.example.com/remoteEntry.js');}if(userPermissions.includes('admin')){awaitloader.loadMicroFrontend('admin','http://admin.example.com/remoteEntry.js');}// Services from micro-frontends are now availableconstanalyticsService=awaitcontainer.get('AnalyticsService');
4. Complex Initialization Sequences
// Initialize services in specific order with dependenciesawaitcontainer.setMany(DatabaseService,// Initialize firstCacheService,// Initialize secondUserService,// Depends on both aboveNotificationService// Initialize last);// Services can have async constructors/initialization
@Service()classEmailService{privatetransporter: any;asyncinitialize(){this.transporter=awaitcreateTransporter({host: process.env.SMTP_HOST,auth: awaitgetEmailCredentials(),});}}
// Async resolution allows for lazy loading and cachingconstservice=awaitcontainer.get(ExpensiveService);// Service is created once and cached for subsequent calls
Benefits Analysis
1. Modern JavaScript Alignment
Embraces async/await patterns that are standard in modern JavaScript
Aligns with Node.js patterns and modern web development practices
Supports modern Promise-based APIs and error handling
2. Enterprise-Ready Features
Supports complex initialization patterns required in enterprise applications
Enables proper resource lifecycle management
Supports dynamic configuration and feature toggling
3. Better Error Handling
Async operations can be properly caught and handled
Initialization errors are propagated correctly through the Promise chain
Better debugging experience with async stack traces
4. Performance Improvements
Parallel registration with setMany() improves startup time
Lazy async resolution reduces memory footprint
Efficient resource cleanup prevents memory leaks
5. Framework Integration
Better integration with async frameworks (Express, Fastify, Next.js)
Supports async middleware and interceptors
Enables proper request-scoped container patterns
6. Micro-Frontend Architecture
Dynamic loading of micro-frontend modules and their dependencies
Runtime feature toggling based on user permissions or A/B tests
Shared service registration across multiple micro-frontends
Isolation and scoping of micro-frontend services
Addressing Developer Friction
The await "Friction" is Actually a Benefit
While adding await keywords might seem like friction, it actually provides several developer benefits:
1. Explicit Async Operations
// Clear intent: this operation might take timeawaitcontainer.set(DatabaseService);// vs unclear sync operation that might hide complexitycontainer.set(DatabaseService);// Is this instantaneous? Does it do I/O?
2. Proper Error Handling
// Can catch initialization errors properlytry{awaitcontainer.set(DatabaseService);}catch(error){console.error('Failed to initialize database:',error);}// vs silent failures in sync versioncontainer.set(DatabaseService);// Errors might be swallowed
3. Better IDE Support
TypeScript/IDE can properly track async operations
Better autocomplete and error detection
Clear indication of operations that might fail
4. Testability
// Easy to test async initializationtest('should initialize database service',async()=>{awaitcontainer.set(DatabaseService);expect(container.has(DatabaseService)).toBe(true);});
Implementation Strategy
Since NexusDI is still in 0.x.y releases and doesn't have established users yet, this is the perfect time to make this foundational architectural decision:
Direct Implementation
Phase 1: Implement async-first architecture in next 0.x release
Phase 2: Update all documentation and examples to reflect async patterns
Phase 3: Gather community feedback on the new architecture
Phase 4: Stabilize API based on feedback before 1.0 release
Advantages of Pre-1.0 Implementation
No breaking changes for existing users (since there are none)
Can iterate on the API design based on community feedback
Establishes the correct architectural foundation from the start
Avoids technical debt that would accumulate with sync-first approach
Comparison with Other Libraries
Spring Framework (Java)
// Spring's ApplicationContext is inherently async-readyApplicationContextcontext = newAnnotationConfigApplicationContext();
// Bean creation can involve async operations
.NET Core DI
// .NET Core supports async service registration and resolutionservices.AddSingletonAsync<IDataService>(async provider =>{varservice=newDataService();awaitservice.InitializeAsync();returnservice;});
NestJS
// NestJS embraces async patterns throughout
@Injectable()exportclassDatabaseServiceimplementsOnModuleInit{asynconModuleInit(){awaitthis.connect();}}
Problem: Doubles API surface area, confusing for developers
Problem: Sync methods can't benefit from async capabilities
2. Opt-in Async: Make async optional
Problem: Limited benefits, complexity in implementation
Problem: Two different mental models for same operations
3. Wrapper Pattern: Async wrapper around sync core
Problem: Leaky abstraction, async benefits are superficial
Problem: Cannot truly support async factories or initialization
Micro-Frontend Architecture Deep Dive
Async-first NexusDI would be particularly powerful for micro-frontend architectures, enabling sophisticated runtime composition patterns:
Shell Application with Dynamic Micro-Frontend Loading
// Shell application's main containerclassShellApplication{privatecontainer=newNexus();privateloadedMicroFrontends=newSet<string>();asyncbootstrap(){// Register shell servicesawaitthis.container.setMany(AuthService,NavigationService,ThemeService,NotificationService);// Load initial micro-frontendsawaitthis.loadUserSpecificMicroFrontends();}asyncloadUserSpecificMicroFrontends(){constuser=awaitthis.container.get(AuthService).getCurrentUser();// Load micro-frontends based on user roleconstmicroFrontends=awaitthis.getMicroFrontendsForUser(user);awaitPromise.all(microFrontends.map((mf)=>this.loadMicroFrontend(mf)));}asyncloadMicroFrontend(config: MicroFrontendConfig){if(this.loadedMicroFrontends.has(config.name)){return;// Already loaded}try{// Create isolated container for micro-frontendconstmfContainer=this.container.createChildContainer();// Load remote moduleconstremoteModule=awaitimport(config.remoteEntry);constmfModule=awaitremoteModule.get('./Module');// Register micro-frontend's moduleawaitmfContainer.set(mfModule.default);// Register micro-frontend specific configurationawaitmfContainer.set({token: `${config.name}_CONFIG`,useValue: config.settings,});// Initialize micro-frontendconstmfBootstrap=awaitmfContainer.get('MicroFrontendBootstrap');awaitmfBootstrap.initialize();this.loadedMicroFrontends.add(config.name);}catch(error){console.error(`Failed to load micro-frontend ${config.name}:`,error);// Graceful degradation - app continues without this micro-frontend}}}
Micro-Frontend Module Definition
// Analytics micro-frontend module
@Module({providers: [AnalyticsService,ReportingService,ChartService,{token: 'DATA_SOURCE',useFactory: async(config: AnalyticsConfig)=>{constdataSource=newAnalyticsDataSource(config.apiUrl);awaitdataSource.connect();returndataSource;},deps: ['ANALYTICS_CONFIG'],},],exports: [AnalyticsService,ReportingService],})exportclassAnalyticsMicroFrontendModule{}// Bootstrap class for the micro-frontend
@Service()exportclassMicroFrontendBootstrap{constructor(privateanalyticsService: AnalyticsService,privatenavigationService: NavigationService// Injected from shell){}asyncinitialize(){// Register routes with shell navigationthis.navigationService.addRoutes([{path: '/analytics',component: 'AnalyticsDashboard'},{path: '/reports',component: 'ReportsPage'},]);// Initialize analytics trackingawaitthis.analyticsService.initialize();}}
Feature Toggling and A/B Testing
// Dynamic feature loading based on feature flagsclassFeatureToggleService{constructor(privatecontainer: Nexus){}asyncloadConditionalFeatures(){constfeatureFlags=awaitthis.getFeatureFlags();// Load features dynamically based on flagsif(featureFlags.newDashboard){constnewDashboard=awaitimport('./features/NewDashboard');awaitthis.container.set(newDashboard.NewDashboardModule);}if(featureFlags.experimentalAnalytics){constanalytics=awaitimport('./features/ExperimentalAnalytics');awaitthis.container.set(analytics.ExperimentalAnalyticsModule);}// A/B test: Load different implementationsconstvariant=awaitthis.getABTestVariant('checkout-flow');constcheckoutModule=awaitimport(`./checkout/${variant}`);awaitthis.container.set(checkoutModule.CheckoutModule);}}
Service Sharing Across Micro-Frontends
// Shared services registryclassSharedServicesRegistry{privatesharedContainer: Nexus;asyncinitialize(){this.sharedContainer=newNexus();// Register commonly shared servicesawaitthis.sharedContainer.setMany(AuthService,EventBus,ApiClient,ConfigService,{token: 'SHARED_CACHE',useFactory: async()=>{constcache=newRedisCache();awaitcache.connect();returncache;},});}// Micro-frontends can access shared servicesgetSharedService<T>(token: TokenType<T>): Promise<T>{returnthis.sharedContainer.get(token);}// Register a service to be shared across micro-frontendsasyncshareService(token: TokenType,provider: any): Promise<void>{awaitthis.sharedContainer.set({ token, ...provider});}}
Benefits for Micro-Frontend Architecture
Runtime Composition: Load micro-frontends based on user permissions, feature flags, or A/B tests
Service Isolation: Each micro-frontend has its own container scope while sharing common services
Graceful Degradation: Failed micro-frontend loads don't crash the shell application
Resource Management: Proper cleanup when micro-frontends are unloaded
Configuration Management: Dynamic configuration loading for each micro-frontend
Dependency Sharing: Shared services prevent duplication across micro-frontends
Questions for Community
Are there specific use cases where async-first would be particularly beneficial for your projects?
What async patterns or capabilities would you like to see supported?
For micro-frontend use cases: What specific patterns would be most valuable for your architecture?
Are there any concerns about the async-first approach that we should address?
What would make the async API feel natural and intuitive for your use cases?
Should we prioritize certain features (async factories, resource management, dynamic loading) over others?
Conclusion
While async-first architecture requires adding await keywords, it transforms NexusDI from a simple service container into a modern, enterprise-ready dependency injection framework capable of handling complex real-world scenarios.
The "friction" of await is outweighed by:
Unlocking impossible use cases (async factories, resource management)
Better error handling and debugging experience
Modern JavaScript alignment with current best practices
Enterprise features required by complex applications
Performance improvements through parallel operations
This change positions NexusDI as a forward-thinking framework ready for the next generation of JavaScript applications.
Next Steps: Community feedback and discussion to refine the proposal and implementation strategy.
help wantedExtra attention is neededquestionFurther information is requested
1 participant
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Status: Draft
Authors: NexusDI Core Team
Summary
This RFC proposes refactoring NexusDI to be async-first, meaning all container operations (
set,get,resolve) return Promises. While this introduces the need forawaitkeywords, it unlocks significant architectural benefits that make the library more powerful, modern, and future-proof.Motivation
Current Limitations
NexusDI's current synchronous architecture creates several constraints:
Real-World Use Cases That Are Currently Impossible
Detailed Design
Core API Changes
Before (Sync)
After (Async-First)
New Capabilities Unlocked
1. Async Factories
2. Dynamic Module Loading
3. Micro-Frontend Architecture Support
4. Complex Initialization Sequences
Performance Optimizations
Parallel Registration
Lazy Async Resolution
Benefits Analysis
1. Modern JavaScript Alignment
2. Enterprise-Ready Features
3. Better Error Handling
4. Performance Improvements
setMany()improves startup time5. Framework Integration
6. Micro-Frontend Architecture
Addressing Developer Friction
The
await"Friction" is Actually a BenefitWhile adding
awaitkeywords might seem like friction, it actually provides several developer benefits:1. Explicit Async Operations
2. Proper Error Handling
3. Better IDE Support
4. Testability
Implementation Strategy
Since NexusDI is still in
0.x.yreleases and doesn't have established users yet, this is the perfect time to make this foundational architectural decision:Direct Implementation
0.xrelease1.0releaseAdvantages of Pre-1.0 Implementation
Comparison with Other Libraries
Spring Framework (Java)
.NET Core DI
NestJS
Implementation Considerations
Performance
Bundle Size
Developer Experience
Alternatives Considered
1. Hybrid Approach: Keep sync API, add async methods
2. Opt-in Async: Make async optional
3. Wrapper Pattern: Async wrapper around sync core
Micro-Frontend Architecture Deep Dive
Async-first NexusDI would be particularly powerful for micro-frontend architectures, enabling sophisticated runtime composition patterns:
Shell Application with Dynamic Micro-Frontend Loading
Micro-Frontend Module Definition
Feature Toggling and A/B Testing
Service Sharing Across Micro-Frontends
Benefits for Micro-Frontend Architecture
Questions for Community
Are there specific use cases where async-first would be particularly beneficial for your projects?
What async patterns or capabilities would you like to see supported?
For micro-frontend use cases: What specific patterns would be most valuable for your architecture?
Are there any concerns about the async-first approach that we should address?
What would make the async API feel natural and intuitive for your use cases?
Should we prioritize certain features (async factories, resource management, dynamic loading) over others?
Conclusion
While async-first architecture requires adding
awaitkeywords, it transforms NexusDI from a simple service container into a modern, enterprise-ready dependency injection framework capable of handling complex real-world scenarios.The "friction" of
awaitis outweighed by:This change positions NexusDI as a forward-thinking framework ready for the next generation of JavaScript applications.
Next Steps: Community feedback and discussion to refine the proposal and implementation strategy.
Beta Was this translation helpful? Give feedback.
All reactions