A social knowledge tool for researchers built on ATProto

refactor: introduce IProcess interface and separate process implementations

Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) <aider@aider.chat>

+144 -110
+10 -36
src/index.ts
··· 1 - import { createExpressApp } from './shared/infrastructure/http/app'; 2 - import { DatabaseFactory } from './shared/infrastructure/database/DatabaseFactory'; 3 import { configService } from './shared/infrastructure/config'; 4 - import { startFeedWorker } from './workers/feed-worker'; 5 - 6 - async function startServer() { 7 - // Get configuration 8 - const config = configService.get(); 9 - 10 - const useMockRepos = process.env.USE_MOCK_REPOS === 'true'; 11 - if (!useMockRepos) { 12 - // Create database connection with config 13 - const db = DatabaseFactory.createConnection( 14 - configService.getDatabaseConfig(), 15 - ); 16 - 17 - // Run migrations 18 - try { 19 - await DatabaseFactory.runMigrations(db); 20 - console.log('Database migrations completed successfully'); 21 - } catch (error) { 22 - console.error('Error running database migrations:', error); 23 - process.exit(1); 24 - } 25 - } 26 27 - // Create and start Express app with config 28 - const app = createExpressApp(configService); 29 - const PORT = config.server.port; 30 - const HOST = config.server.host; 31 32 - app.listen(PORT, HOST, () => { 33 - console.log( 34 - `Server running on http://${HOST}:${PORT} in ${config.environment} environment`, 35 - ); 36 - }); 37 38 // Start feed worker in the same process if using in-memory events 39 const useInMemoryEvents = process.env.USE_IN_MEMORY_EVENTS === 'true'; 40 if (useInMemoryEvents) { 41 console.log('Starting feed worker in the same process...'); 42 - await startFeedWorker(); 43 } 44 } 45 46 - startServer().catch((error) => { 47 - console.error('Failed to start server:', error); 48 process.exit(1); 49 });
··· 1 import { configService } from './shared/infrastructure/config'; 2 + import { AppProcess } from './shared/infrastructure/processes/AppProcess'; 3 + import { FeedWorkerProcess } from './shared/infrastructure/processes/FeedWorkerProcess'; 4 5 + async function main() { 6 + const appProcess = new AppProcess(configService); 7 8 + // Start the app process 9 + await appProcess.start(); 10 11 // Start feed worker in the same process if using in-memory events 12 const useInMemoryEvents = process.env.USE_IN_MEMORY_EVENTS === 'true'; 13 if (useInMemoryEvents) { 14 console.log('Starting feed worker in the same process...'); 15 + const feedWorkerProcess = new FeedWorkerProcess(configService); 16 + await feedWorkerProcess.start(); 17 } 18 } 19 20 + main().catch((error) => { 21 + console.error('Failed to start application:', error); 22 process.exit(1); 23 });
+3
src/shared/domain/IProcess.ts
···
··· 1 + export interface IProcess { 2 + start(): Promise<void>; 3 + }
+41
src/shared/infrastructure/processes/AppProcess.ts
···
··· 1 + import { IProcess } from '../../domain/IProcess'; 2 + import { createExpressApp } from '../http/app'; 3 + import { DatabaseFactory } from '../database/DatabaseFactory'; 4 + import { EnvironmentConfigService } from '../config/EnvironmentConfigService'; 5 + 6 + export class AppProcess implements IProcess { 7 + constructor(private configService: EnvironmentConfigService) {} 8 + 9 + async start(): Promise<void> { 10 + // Get configuration 11 + const config = this.configService.get(); 12 + 13 + const useMockRepos = process.env.USE_MOCK_REPOS === 'true'; 14 + if (!useMockRepos) { 15 + // Create database connection with config 16 + const db = DatabaseFactory.createConnection( 17 + this.configService.getDatabaseConfig(), 18 + ); 19 + 20 + // Run migrations 21 + try { 22 + await DatabaseFactory.runMigrations(db); 23 + console.log('Database migrations completed successfully'); 24 + } catch (error) { 25 + console.error('Error running database migrations:', error); 26 + process.exit(1); 27 + } 28 + } 29 + 30 + // Create and start Express app with config 31 + const app = createExpressApp(this.configService); 32 + const PORT = config.server.port; 33 + const HOST = config.server.host; 34 + 35 + app.listen(PORT, HOST, () => { 36 + console.log( 37 + `Server running on http://${HOST}:${PORT} in ${config.environment} environment`, 38 + ); 39 + }); 40 + } 41 + }
+84
src/shared/infrastructure/processes/FeedWorkerProcess.ts
···
··· 1 + import { IProcess } from '../../domain/IProcess'; 2 + import { EnvironmentConfigService } from '../config/EnvironmentConfigService'; 3 + import { RepositoryFactory } from '../http/factories/RepositoryFactory'; 4 + import { ServiceFactory } from '../http/factories/ServiceFactory'; 5 + import { UseCaseFactory } from '../http/factories/UseCaseFactory'; 6 + import { CardAddedToLibraryEventHandler } from '../../../modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler'; 7 + import { CardAddedToCollectionEventHandler } from '../../../modules/feeds/application/eventHandlers/CardAddedToCollectionEventHandler'; 8 + import { CardCollectionSaga } from '../../../modules/feeds/application/sagas/CardCollectionSaga'; 9 + import { QueueNames } from '../events/QueueConfig'; 10 + import { EventNames } from '../events/EventConfig'; 11 + 12 + export class FeedWorkerProcess implements IProcess { 13 + constructor(private configService: EnvironmentConfigService) {} 14 + 15 + async start(): Promise<void> { 16 + console.log('Starting feed worker...'); 17 + 18 + // Create dependencies using factories 19 + const repositories = RepositoryFactory.create(this.configService); 20 + const services = ServiceFactory.createForWorker( 21 + this.configService, 22 + repositories, 23 + ); 24 + const useCases = UseCaseFactory.createForWorker(repositories, services); 25 + 26 + // Test Redis connection (only if using Redis) 27 + if (services.redisConnection) { 28 + try { 29 + await services.redisConnection.ping(); 30 + console.log('Connected to Redis successfully'); 31 + } catch (error) { 32 + console.error('Failed to connect to Redis:', error); 33 + process.exit(1); 34 + } 35 + } else { 36 + console.log('Using in-memory event system'); 37 + } 38 + 39 + // Create subscriber for feeds queue 40 + const eventSubscriber = services.createEventSubscriber(QueueNames.FEEDS); 41 + 42 + // Create the saga with proper dependencies 43 + const cardCollectionSaga = new CardCollectionSaga( 44 + useCases.addActivityToFeedUseCase, 45 + ); 46 + 47 + // Create event handlers with the saga 48 + const cardAddedToLibraryHandler = new CardAddedToLibraryEventHandler( 49 + cardCollectionSaga, 50 + ); 51 + const cardAddedToCollectionHandler = new CardAddedToCollectionEventHandler( 52 + cardCollectionSaga, 53 + ); 54 + 55 + // Register handlers 56 + await eventSubscriber.subscribe( 57 + EventNames.CARD_ADDED_TO_LIBRARY, 58 + cardAddedToLibraryHandler, 59 + ); 60 + 61 + await eventSubscriber.subscribe( 62 + EventNames.CARD_ADDED_TO_COLLECTION, 63 + cardAddedToCollectionHandler, 64 + ); 65 + 66 + // Start the worker 67 + await eventSubscriber.start(); 68 + 69 + console.log('Feed worker started and listening for events...'); 70 + 71 + // Graceful shutdown 72 + const shutdown = async () => { 73 + console.log('Shutting down feed worker...'); 74 + await eventSubscriber.stop(); 75 + if (services.redisConnection) { 76 + await services.redisConnection.quit(); 77 + } 78 + process.exit(0); 79 + }; 80 + 81 + process.on('SIGTERM', shutdown); 82 + process.on('SIGINT', shutdown); 83 + } 84 + }
+6 -74
src/workers/feed-worker.ts
··· 1 import { EnvironmentConfigService } from '../shared/infrastructure/config/EnvironmentConfigService'; 2 - import { RepositoryFactory } from '../shared/infrastructure/http/factories/RepositoryFactory'; 3 - import { ServiceFactory } from '../shared/infrastructure/http/factories/ServiceFactory'; 4 - import { UseCaseFactory } from '../shared/infrastructure/http/factories/UseCaseFactory'; 5 - import { CardAddedToLibraryEventHandler } from '../modules/feeds/application/eventHandlers/CardAddedToLibraryEventHandler'; 6 - import { CardAddedToCollectionEventHandler } from '../modules/feeds/application/eventHandlers/CardAddedToCollectionEventHandler'; 7 - import { CardCollectionSaga } from '../modules/feeds/application/sagas/CardCollectionSaga'; 8 - import { QueueNames } from '../shared/infrastructure/events/QueueConfig'; 9 - import { EventNames } from '../shared/infrastructure/events/EventConfig'; 10 - 11 - export async function startFeedWorker() { 12 - console.log('Starting feed worker...'); 13 14 const configService = new EnvironmentConfigService(); 15 - 16 - // Create dependencies using factories 17 - const repositories = RepositoryFactory.create(configService); 18 - const services = ServiceFactory.createForWorker(configService, repositories); 19 - const useCases = UseCaseFactory.createForWorker(repositories, services); 20 - 21 - // Test Redis connection (only if using Redis) 22 - if (services.redisConnection) { 23 - try { 24 - await services.redisConnection.ping(); 25 - console.log('Connected to Redis successfully'); 26 - } catch (error) { 27 - console.error('Failed to connect to Redis:', error); 28 - process.exit(1); 29 - } 30 - } else { 31 - console.log('Using in-memory event system'); 32 - } 33 - 34 - // Create subscriber for feeds queue 35 - const eventSubscriber = services.createEventSubscriber(QueueNames.FEEDS); 36 - 37 - // Create the saga with proper dependencies 38 - const cardCollectionSaga = new CardCollectionSaga( 39 - useCases.addActivityToFeedUseCase, 40 - ); 41 - 42 - // Create event handlers with the saga 43 - const cardAddedToLibraryHandler = new CardAddedToLibraryEventHandler( 44 - cardCollectionSaga, 45 - ); 46 - const cardAddedToCollectionHandler = new CardAddedToCollectionEventHandler( 47 - cardCollectionSaga, 48 - ); 49 - 50 - // Register handlers 51 - await eventSubscriber.subscribe( 52 - EventNames.CARD_ADDED_TO_LIBRARY, 53 - cardAddedToLibraryHandler, 54 - ); 55 - 56 - await eventSubscriber.subscribe( 57 - EventNames.CARD_ADDED_TO_COLLECTION, 58 - cardAddedToCollectionHandler, 59 - ); 60 - 61 - // Start the worker 62 - await eventSubscriber.start(); 63 - 64 - console.log('Feed worker started and listening for events...'); 65 - 66 - // Graceful shutdown 67 - const shutdown = async () => { 68 - console.log('Shutting down feed worker...'); 69 - await eventSubscriber.stop(); 70 - if (services.redisConnection) { 71 - await services.redisConnection.quit(); 72 - } 73 - process.exit(0); 74 - }; 75 76 - process.on('SIGTERM', shutdown); 77 - process.on('SIGINT', shutdown); 78 } 79 if (process.env.USE_IN_MEMORY_EVENTS !== 'true') { 80 - startFeedWorker().catch((error) => { 81 console.error('Failed to start feed worker:', error); 82 process.exit(1); 83 });
··· 1 import { EnvironmentConfigService } from '../shared/infrastructure/config/EnvironmentConfigService'; 2 + import { FeedWorkerProcess } from '../shared/infrastructure/processes/FeedWorkerProcess'; 3 4 + async function main() { 5 const configService = new EnvironmentConfigService(); 6 + const feedWorkerProcess = new FeedWorkerProcess(configService); 7 8 + await feedWorkerProcess.start(); 9 } 10 + 11 if (process.env.USE_IN_MEMORY_EVENTS !== 'true') { 12 + main().catch((error) => { 13 console.error('Failed to start feed worker:', error); 14 process.exit(1); 15 });