# Dependency Injection Migration Plan ## Overview This document outlines the migration approach for converting legacy exported singleton services to the dependency injection pattern, following the successful authentication architecture refactoring. ## Legacy Pattern vs New Pattern ### ❌ Legacy Pattern (To Be Replaced) ```typescript // Service definition with exported singleton export class SomeService { // service implementation } export const someService = new SomeService() // Consumer code import { someService } from '@/services/someService' someService.doSomething() ``` ### ✅ New Dependency Injection Pattern ```typescript // Service definition (no exported singleton) export class SomeService extends BaseService { // service implementation } // Service registration in module container.provide(SERVICE_TOKENS.SOME_SERVICE, someService) // Consumer code const someService = injectService(SERVICE_TOKENS.SOME_SERVICE) as SomeService someService.doSomething() ``` ## Services to Migrate ### Priority 1: High Impact Services #### 1. InvoiceService - **File**: `src/core/services/invoiceService.ts` - **Current**: `export const invoiceService = new InvoiceService()` - **Token**: Need to add `INVOICE_SERVICE: Symbol('invoiceService')` - **Usage**: Market store and components for payment processing - **Impact**: Core business logic, high usage - **Dependencies**: Likely uses LnbitsAPI and other services #### 2. NostrmarketService - **File**: `src/modules/market/services/nostrmarketService.ts` - **Current**: `export const nostrmarketService = new NostrmarketService()` - **Token**: Need to add `NOSTRMARKET_SERVICE: Symbol('nostrmarketService')` - **Usage**: Market module composables and components - **Impact**: Key module service, should follow DI pattern - **Dependencies**: Likely uses RelayHub and other Nostr services ### Priority 2: Medium Impact Services #### 3. PaymentMonitorService - **File**: `src/modules/market/services/paymentMonitor.ts` - **Current**: `export const paymentMonitor = new PaymentMonitorService()` - **Token**: `SERVICE_TOKENS.PAYMENT_MONITOR` (already exists) - **Status**: Partially migrated - token exists but service not properly registered - **Usage**: Market store - **Impact**: Background service, less direct component usage #### 4. LnbitsAPI - **File**: `src/lib/api/lnbits.ts` - **Current**: `export const lnbitsAPI = new LnbitsAPI()` - **Token**: Need to add `LNBITS_API: Symbol('lnbitsAPI')` - **Usage**: AuthService and other core services - **Impact**: API layer foundation, widely used but lower-level ## Migration Steps ### Step 1: Add SERVICE_TOKENS Update `src/core/di-container.ts` to add missing tokens: ```typescript export const SERVICE_TOKENS = { // ... existing tokens ... // New service tokens INVOICE_SERVICE: Symbol('invoiceService'), NOSTRMARKET_SERVICE: Symbol('nostrmarketService'), LNBITS_API: Symbol('lnbitsAPI'), // PAYMENT_MONITOR already exists } as const ``` ### Step 2: Register Services in Modules #### Base Module Registration For core services like InvoiceService and LnbitsAPI: ```typescript // src/modules/base/index.ts import { InvoiceService } from '@/core/services/invoiceService' import { LnbitsAPI } from '@/lib/api/lnbits' const invoiceService = new InvoiceService() const lnbitsAPI = new LnbitsAPI() container.provide(SERVICE_TOKENS.INVOICE_SERVICE, invoiceService) container.provide(SERVICE_TOKENS.LNBITS_API, lnbitsAPI) ``` #### Market Module Registration For market-specific services: ```typescript // src/modules/market/index.ts import { NostrmarketService } from './services/nostrmarketService' import { PaymentMonitorService } from './services/paymentMonitor' const nostrmarketService = new NostrmarketService() const paymentMonitor = new PaymentMonitorService() container.provide(SERVICE_TOKENS.NOSTRMARKET_SERVICE, nostrmarketService) container.provide(SERVICE_TOKENS.PAYMENT_MONITOR, paymentMonitor) ``` ### Step 3: Update Service Classes Convert services to extend BaseService for dependency injection: ```typescript // Before export class SomeService { constructor() { // direct imports/dependencies } } // After export class SomeService extends BaseService { protected readonly metadata = { name: 'SomeService', dependencies: ['AuthService', 'RelayHub'] // declare dependencies } // Access dependencies through DI private get authService(): AuthService { return this.dependencies.get('AuthService') as AuthService } } ``` ### Step 4: Update Consumer Code Replace direct imports with dependency injection: ```typescript // Before import { invoiceService } from '@/core/services/invoiceService' // After import { injectService, SERVICE_TOKENS } from '@/core/di-container' const invoiceService = injectService(SERVICE_TOKENS.INVOICE_SERVICE) as InvoiceService ``` ### Step 5: Remove Exported Singletons After migration, remove the exported singleton from service files: ```typescript // Remove this line export const someService = new SomeService() ``` ## Migration Order 1. **LnbitsAPI** - Foundation API layer, needed by other services 2. **InvoiceService** - High impact business logic 3. **PaymentMonitorService** - Complete partial migration 4. **NostrmarketService** - Module-specific service ## Validation Steps For each migrated service: 1. ✅ Service registered in appropriate module 2. ✅ SERVICE_TOKEN added to di-container.ts 3. ✅ All consumers updated to use injectService() 4. ✅ Exported singleton removed from service file 5. ✅ Service extends BaseService if applicable 6. ✅ Dependencies declared in metadata 7. ✅ No build errors or TypeScript warnings 8. ✅ Functionality testing - service works as expected ## Benefits of Migration ### Architecture Benefits - **Single Source of Truth**: Services managed by DI container - **Loose Coupling**: Components depend on interfaces, not concrete classes - **Testability**: Easy to inject mock services for testing - **Module Boundaries**: Clear service ownership and lifecycle ### Maintainability Benefits - **Centralized Registration**: All services registered in module definitions - **Consistent Patterns**: Same DI pattern across all services - **Service Lifecycle**: Container manages service instantiation and disposal - **Dependency Visibility**: Service dependencies clearly declared ### Performance Benefits - **No Duplicate Instances**: Container ensures singleton behavior - **Lazy Loading**: Services only created when needed - **Efficient Memory Usage**: Single instances shared across modules ## Testing Strategy Each migrated service should include: 1. **Unit Tests**: Test service logic with mocked dependencies 2. **Integration Tests**: Test service with real dependencies via DI 3. **Mock Service Creation**: For testing consumers Example mock setup: ```typescript // Test setup const mockInvoiceService = { createInvoice: vi.fn(), checkPayment: vi.fn() } container.provide(SERVICE_TOKENS.INVOICE_SERVICE, mockInvoiceService) ``` ## Risk Mitigation - **Incremental Migration**: One service at a time - **Backward Compatibility**: Keep old imports temporarily during migration - **Comprehensive Testing**: Validate each service after migration - **Rollback Plan**: Git commits allow easy rollback if issues arise ## Migration Status ### ✅ Completed Migrations #### 1. InvoiceService (Completed) - **Status**: ✅ Successfully migrated - **Token**: `SERVICE_TOKENS.INVOICE_SERVICE` - **Registration**: Base module (`src/modules/base/index.ts`) - **Usage**: Market store (`src/modules/market/stores/market.ts`) **Before:** ```typescript // ❌ Legacy singleton pattern import { invoiceService } from '@/core/services/invoiceService' const invoice = await invoiceService.createInvoice(order, adminKey) ``` **After:** ```typescript // ✅ Dependency injection pattern const invoiceService = injectService(SERVICE_TOKENS.INVOICE_SERVICE) as InvoiceService const invoice = await invoiceService.createInvoice(order, adminKey) ``` ### 🔄 In Progress #### 2. NostrmarketService (Pending) - **Status**: 🔄 Ready for migration - **File**: `src/modules/market/services/nostrmarketService.ts` - **Token**: Need to add `NOSTRMARKET_SERVICE` - **Registration**: Market module #### 3. PaymentMonitorService (Partially Migrated) - **Status**: 🔄 Token exists, needs completion - **Token**: `SERVICE_TOKENS.PAYMENT_MONITOR` (already exists) - **Registration**: Market module #### 4. LnbitsAPI (Pending) - **Status**: 🔄 Ready for migration - **File**: `src/lib/api/lnbits.ts` - **Token**: Need to add `LNBITS_API` - **Registration**: Base module ## Practical Example: Market Module Using InvoiceService Here's a real-world example of how the Market module now uses the migrated InvoiceService through dependency injection: ### Market Store Implementation ```typescript // src/modules/market/stores/market.ts import { defineStore } from 'pinia' import { injectService, SERVICE_TOKENS } from '@/core/di-container' import type { InvoiceService } from '@/core/services/invoiceService' export const useMarketStore = defineStore('market', () => { // Inject InvoiceService via dependency injection const invoiceService = injectService(SERVICE_TOKENS.INVOICE_SERVICE) as InvoiceService const createOrderInvoice = async (order: Order, adminKey: string) => { try { // Use injected service to create Lightning invoice const invoice = await invoiceService.createInvoice(order, adminKey, { tag: "nostrmarket", order_id: order.id, merchant_pubkey: order.sellerPubkey, expiry: 3600 }) console.log('Invoice created:', { paymentHash: invoice.payment_hash, bolt11: invoice.bolt11, amount: invoice.amount }) return invoice } catch (error) { console.error('Failed to create order invoice:', error) throw error } } return { createOrderInvoice, // ... other store methods } }) ``` ### Benefits Demonstrated 1. **Service Injection**: `injectService(SERVICE_TOKENS.INVOICE_SERVICE)` provides the service 2. **Type Safety**: TypeScript knows the exact service type with `as InvoiceService` 3. **Testability**: Easy to mock `INVOICE_SERVICE` for testing 4. **Modularity**: Market module depends on base module's InvoiceService 5. **Single Source**: One InvoiceService instance shared across the app ### Service Registration (Base Module) ```typescript // src/modules/base/index.ts import { InvoiceService } from '@/core/services/invoiceService' const invoiceService = new InvoiceService() export const baseModule: ModulePlugin = { async install(_app: App) { // Register InvoiceService in DI container container.provide(SERVICE_TOKENS.INVOICE_SERVICE, invoiceService) // Other service registrations... } } ``` ## Success Metrics ### Completed Services - ✅ **InvoiceService**: Successfully migrated to DI pattern - ✅ **AuthService**: Previously migrated to DI pattern ### Remaining Tasks - 🔄 **NostrmarketService**: Needs migration - 🔄 **PaymentMonitorService**: Complete partial migration - 🔄 **LnbitsAPI**: Needs migration ### Overall Progress - ✅ 2/5 services migrated to dependency injection - ✅ Zero build errors - ✅ Consistent DI patterns established - ✅ Documentation updated with examples