- Change the active file in workspace.json to point to the new modular-design.md. - Revise modular-design.md to enhance clarity and structure, including a new table of contents and updated sections on design philosophy, architecture components, and service abstractions. - Remove outdated content and improve the overall presentation of modular design patterns and best practices. These updates aim to streamline the documentation for better accessibility and understanding of the modular architecture.
382 lines
No EOL
12 KiB
Markdown
382 lines
No EOL
12 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)
|
|
```
|
|
|
|
#### 2. NostrmarketService (Completed)
|
|
- **Status**: ✅ Successfully migrated
|
|
- **Token**: `SERVICE_TOKENS.NOSTRMARKET_SERVICE`
|
|
- **Registration**: Market module (`src/modules/market/index.ts`)
|
|
- **Usage**: Market module composables and stores
|
|
|
|
**Before:**
|
|
```typescript
|
|
// ❌ Legacy singleton pattern
|
|
import { nostrmarketService } from '@/modules/market/services/nostrmarketService'
|
|
await nostrmarketService.publishOrder(orderData)
|
|
```
|
|
|
|
**After:**
|
|
```typescript
|
|
// ✅ Dependency injection pattern
|
|
const nostrmarketService = injectService(SERVICE_TOKENS.NOSTRMARKET_SERVICE) as NostrmarketService
|
|
await nostrmarketService.publishOrder(orderData)
|
|
```
|
|
|
|
#### 3. PaymentMonitorService (Completed)
|
|
- **Status**: ✅ Successfully migrated
|
|
- **Token**: `SERVICE_TOKENS.PAYMENT_MONITOR`
|
|
- **Registration**: Market module (`src/modules/market/index.ts`)
|
|
- **Usage**: Market store for payment status monitoring
|
|
|
|
#### 4. LnbitsAPI (Completed)
|
|
- **Status**: ✅ Successfully migrated
|
|
- **Token**: `SERVICE_TOKENS.LNBITS_API`
|
|
- **Registration**: Base module (`src/modules/base/index.ts`)
|
|
- **Usage**: AuthService and events API integration
|
|
|
|
## 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 |