- Add INVOICE_SERVICE token to DI container - Register InvoiceService in base module with proper lifecycle - Update market store to use dependency injection instead of singleton - Remove exported singleton from InvoiceService class - Add comprehensive migration documentation with examples - Maintain type safety with proper TypeScript interfaces This migration eliminates the legacy singleton pattern and improves: - Testability through service injection - Modular architecture with clear boundaries - Single source of truth for service instances - Consistent dependency injection patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
369 lines
No EOL
11 KiB
Markdown
369 lines
No EOL
11 KiB
Markdown
# 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 |