Migrate InvoiceService to dependency injection pattern
- 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>
This commit is contained in:
parent
6cb10a31db
commit
7a32085ee1
5 changed files with 383 additions and 6 deletions
369
docs/04-migrations/dependency-injection-migration.md
Normal file
369
docs/04-migrations/dependency-injection-migration.md
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue