- 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.
12 KiB
🔧 Modular Design Patterns
Plugin-based architecture enabling scalable, maintainable, and extensible feature development with dependency injection, service abstractions, and clean modular boundaries.
Table of Contents
- #Design Philosophy
- #Architecture Components
- #Service Abstractions
- #Module Development
- #Implementation Patterns
- #Benefits & Trade-offs
Design Philosophy
Separation of Concerns
Each module owns its complete domain logic, from data models to UI components, ensuring clear boundaries and reducing coupling between features.
Dependency Injection
Services communicate through well-defined interfaces using a centralized DI container, enabling loose coupling and testability.
Plugin-Based Development
Features are implemented as self-contained modules that can be independently developed, tested, and deployed.
Reactive Architecture
Services integrate Vue's reactivity system to provide automatic UI updates and consistent state management.
Architecture Components
Plugin Manager
Orchestrates module loading, dependency resolution, and lifecycle management:
class PluginManager {
async loadModules(modules: ModulePlugin[]): Promise<void>
getDependencyGraph(): Map<string, string[]>
validateDependencies(modules: ModulePlugin[]): void
}
Dependency Injection Container
Manages service registration, injection, and lifecycle:
// Service registration
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
// Service consumption
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
BaseService Architecture
Abstract foundation providing common patterns for all services:
abstract class BaseService {
protected readonly metadata: ServiceMetadata
protected dependencies = new Map<string, any>()
abstract initialize(): Promise<void>
abstract dispose(): Promise<void>
}
Module Plugin Interface
Standardized contract for all feature modules:
interface ModulePlugin {
name: string
version: string
dependencies: string[]
install(app: App, options?: ModuleConfig): Promise<void>
routes?: RouteRecordRaw[]
components?: Record<string, Component>
}
Service Abstractions
Core Infrastructure Services
AuthService - User Identity Management
- Purpose: Centralized authentication with Nostr key handling
- Features: Session management, profile updates, secure key storage
- Pattern: BaseService with reactive state management
RelayHub - Nostr Connection Management
- Purpose: Centralized relay connections and event processing
- Features: Connection pooling, automatic reconnection, event deduplication
- Pattern: Service singleton with connection state management
InvoiceService - Lightning Payment Processing
- Purpose: Unified Lightning Network integration
- Features: Invoice creation, payment monitoring, QR code generation
- Pattern: BaseService with LNBits API integration
ToastService - User Notifications
- Purpose: Consistent notification system across modules
- Features: Context-specific messages, accessibility support
- Pattern: Service with method categorization by context
StorageService - User-Scoped Data Persistence
- Purpose: Secure, user-isolated local storage operations
- Features: Encryption, type safety, reactive storage
- Pattern: Service with automatic user prefixing
Shared Composables
useAuth() - Authentication Access
const auth = useAuth()
const { isAuthenticated, currentUser, login, logout } = auth
useToast() - Notification Access
const toast = useToast()
toast.success('Operation completed!')
toast.error('Operation failed')
useStorage() - Storage Access
const storage = useStorage()
const userData = storage.getUserData('preferences', defaultPrefs)
Module Development
Module Structure Pattern
Each module follows a consistent directory structure:
src/modules/[module-name]/
├── index.ts # Module plugin definition
├── components/ # Module-specific UI components
├── composables/ # Module composables and hooks
├── services/ # Business logic services
├── stores/ # Module-specific Pinia stores
├── types/ # TypeScript type definitions
└── views/ # Page components and routes
Service Development Pattern
Services extend BaseService for consistent lifecycle management:
export class MyService extends BaseService {
protected readonly metadata = {
name: 'MyService',
dependencies: ['AuthService', 'RelayHub']
}
constructor() {
super()
}
async initialize(): Promise<void> {
await super.initialize() // Initialize dependencies
// Service-specific initialization
this.isInitialized.value = true
}
async dispose(): Promise<void> {
// Cleanup logic
this.isDisposed.value = true
}
}
Module Registration Pattern
Standardized module plugin structure:
export const myModule: ModulePlugin = {
name: 'my-module',
version: '1.0.0',
dependencies: ['base'],
async install(app: App, options?: MyModuleConfig) {
// 1. Create and register services
const myService = new MyService()
container.provide(SERVICE_TOKENS.MY_SERVICE, myService)
// 2. Register global components
app.component('MyGlobalComponent', MyGlobalComponent)
// 3. Initialize services
await myService.initialize()
},
routes: [
{
path: '/my-feature',
component: () => import('./views/MyFeatureView.vue')
}
]
}
Dependency Declaration
Services declare dependencies through metadata:
protected readonly metadata = {
name: 'ChatService',
dependencies: ['AuthService', 'RelayHub', 'StorageService']
}
// Access injected dependencies
private get authService(): AuthService {
return this.dependencies.get('AuthService') as AuthService
}
Implementation Patterns
Cross-Module Communication
Service Dependencies
For required functionality between modules:
// ✅ Correct: Use dependency injection
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
// ❌ Wrong: Direct import breaks modularity
import { relayHub } from '../base/services/relay-hub'
Event Bus Communication
For optional cross-module notifications:
// Publishing events
eventBus.emit('user:authenticated', { userId: user.pubkey })
eventBus.emit('payment:received', { amount: 1000, invoiceId: 'abc123' })
// Subscribing to events
eventBus.on('chat:message-received', (message) => {
// Handle cross-module events
})
Shared Components
Modules can export components for reuse:
// In module plugin definition
export const chatModule: ModulePlugin = {
components: {
'ChatAvatar': () => import('./components/ChatAvatar.vue'),
'MessageBubble': () => import('./components/MessageBubble.vue')
}
}
// Usage in other modules
<ChatAvatar :pubkey="user.pubkey" :size="32" />
State Management Patterns
Service-Based State
Core state managed by services:
// AuthService manages authentication state
export class AuthService extends BaseService {
public isAuthenticated = ref(false)
public user = ref<User | null>(null)
// Computed properties for components
public userDisplay = computed(() => this.user.value?.name || 'Anonymous')
}
Module-Specific Stores
Complex state handled by Pinia stores:
// Market module store
export const useMarketStore = defineStore('market', () => {
const products = ref<Product[]>([])
const cartItems = ref<CartItem[]>([])
// Access shared services
const auth = useAuth()
const toast = useToast()
return { products, cartItems }
})
Error Handling Patterns
Service-Level Error Handling
Centralized error handling in services:
export class MyService extends BaseService {
protected handleError(error: Error, context: string) {
console.error(`[${this.metadata.name}] ${context}:`, error)
// Use toast service for user notifications
const toast = injectService(SERVICE_TOKENS.TOAST_SERVICE)
toast.error(`${context} failed: ${error.message}`)
}
}
Component-Level Error Handling
Consistent error display in components:
<script setup lang="ts">
const { error, isLoading, execute } = useAsyncOperation()
const handleAction = () => execute(async () => {
await myService.performAction()
})
</script>
<template>
<div>
<button @click="handleAction" :disabled="isLoading">
{{ isLoading ? 'Loading...' : 'Perform Action' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
Benefits & Trade-offs
Achieved Benefits
Development Velocity
- Consistent Patterns - Standardized development approaches across modules
- Reusable Services - Shared infrastructure reduces implementation time
- Type Safety - Dependency injection eliminates
as anycasting - Clear Boundaries - Module separation simplifies feature development
Maintainability
- Single Source of Truth - Centralized services eliminate duplication
- Dependency Visibility - Clear service relationships and requirements
- Testability - Isolated modules and mockable dependencies
- Extensibility - Easy to add new modules without affecting existing ones
User Experience
- Consistent Interface - Unified patterns across all features
- Reliable Performance - Optimized shared services
- Progressive Loading - Lazy-loaded modules for faster initial load
- Error Recovery - Consistent error handling and user feedback
Architectural Trade-offs
Complexity vs. Flexibility
- Increased Initial Setup - More boilerplate for dependency injection
- Enhanced Flexibility - Easy to swap implementations and add features
- Learning Curve - Developers need to understand DI patterns
- Long-term Benefits - Reduced complexity as application grows
Performance Considerations
- Service Initialization - Dependency resolution overhead at startup
- Memory Efficiency - Singleton services reduce memory usage
- Bundle Optimization - Module-based code splitting improves loading
- Runtime Performance - Optimized shared services benefit all modules
Best Practices Established
- Always use dependency injection for cross-module service access
- Extend BaseService for all business logic services
- Declare dependencies explicitly in service metadata
- Use reactive state for UI-affecting service properties
- Handle errors consistently through standardized patterns
- Test modules in isolation with mocked dependencies
See Also
Architecture Documentation
- index - System design principles
- dependency-injection - DI container system
- authentication-architecture - Auth service patterns
Development Guides
- ../04-development/index - Module development workflows
- ../02-modules/index - Individual module documentation
- ../03-core-services/index - Infrastructure service details
Tags: #architecture #modular-design #dependency-injection #plugin-system
Last Updated: 2025-09-07
Author: Development Team