huge refactor with a million of things to make the code much more managable and easier to create new services #3
69 changed files with 41 additions and 2956 deletions
|
|
@ -40,7 +40,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
**Microservices Architecture** with shared libraries and multi-database storage:
|
**Microservices Architecture** with shared libraries and multi-database storage:
|
||||||
|
|
||||||
### Core Services (`apps/`)
|
### Core Services (`apps/`)
|
||||||
- **data-service** - Market data ingestion from multiple providers (Yahoo, QuoteMedia, IB)
|
- **data-ingestion** - Market data ingestion from multiple providers (Yahoo, QuoteMedia, IB)
|
||||||
- **processing-service** - Data cleaning, validation, and technical indicators
|
- **processing-service** - Data cleaning, validation, and technical indicators
|
||||||
- **strategy-service** - Trading strategies and backtesting (multi-mode: live, event-driven, vectorized, hybrid)
|
- **strategy-service** - Trading strategies and backtesting (multi-mode: live, event-driven, vectorized, hybrid)
|
||||||
- **execution-service** - Order management and risk controls
|
- **execution-service** - Order management and risk controls
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"service": {
|
"service": {
|
||||||
"name": "data-service",
|
"name": "data-ingestion",
|
||||||
"port": 2001,
|
"port": 2001,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"healthCheckPath": "/health",
|
"healthCheckPath": "/health",
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@stock-bot/data-service",
|
"name": "@stock-bot/data-ingestion",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Combined data ingestion and historical data service",
|
"description": "Market data ingestion from multiple providers with proxy support and rate limiting",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -29,7 +29,7 @@ if (config.log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create logger AFTER config is set
|
// Create logger AFTER config is set
|
||||||
const logger = getLogger('data-service');
|
const logger = getLogger('data-ingestion');
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ const health = new Hono();
|
||||||
health.get('/', c => {
|
health.get('/', c => {
|
||||||
return c.json({
|
return c.json({
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
service: 'data-service',
|
service: 'data-ingestion',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"service": {
|
"service": {
|
||||||
"name": "data-sync-service",
|
"name": "data-pipeline",
|
||||||
"port": 3005,
|
"port": 3005,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"healthCheckPath": "/health",
|
"healthCheckPath": "/health",
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@stock-bot/data-sync-service",
|
"name": "@stock-bot/data-pipeline",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Sync service from MongoDB raw data to PostgreSQL master records",
|
"description": "Data processing pipeline for syncing and transforming raw data to normalized records",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -9,7 +9,7 @@ import { connectPostgreSQL } from '@stock-bot/postgres-client';
|
||||||
import { QueueManager, type QueueManagerConfig } from '@stock-bot/queue';
|
import { QueueManager, type QueueManagerConfig } from '@stock-bot/queue';
|
||||||
import { Shutdown } from '@stock-bot/shutdown';
|
import { Shutdown } from '@stock-bot/shutdown';
|
||||||
// Local imports
|
// Local imports
|
||||||
import { healthRoutes, enhancedSyncRoutes, statsRoutes, syncRoutes } from './routes';
|
import { enhancedSyncRoutes, healthRoutes, statsRoutes, syncRoutes } from './routes';
|
||||||
|
|
||||||
const config = initializeServiceConfig();
|
const config = initializeServiceConfig();
|
||||||
console.log('Data Sync Service Configuration:', JSON.stringify(config, null, 2));
|
console.log('Data Sync Service Configuration:', JSON.stringify(config, null, 2));
|
||||||
|
|
@ -28,7 +28,7 @@ if (config.log) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create logger AFTER config is set
|
// Create logger AFTER config is set
|
||||||
const logger = getLogger('data-sync-service');
|
const logger = getLogger('data-pipeline');
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
|
@ -6,9 +6,9 @@ const health = new Hono();
|
||||||
health.get('/', c => {
|
health.get('/', c => {
|
||||||
return c.json({
|
return c.json({
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
service: 'data-sync-service',
|
service: 'data-pipeline',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export { health as healthRoutes };
|
export { health as healthRoutes };
|
||||||
|
|
@ -173,4 +173,4 @@ The functional approach automatically handles cache initialization. No need to m
|
||||||
|
|
||||||
## Need Help?
|
## Need Help?
|
||||||
|
|
||||||
Check the examples in `apps/data-service/src/examples/batch-processing-examples.ts` for more detailed usage patterns.
|
Check the examples in `apps/data-ingestion/src/examples/batch-processing-examples.ts` for more detailed usage patterns.
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
||||||
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
|
import {
|
||||||
import {
|
getConfig,
|
||||||
initializeConfig,
|
|
||||||
initializeServiceConfig,
|
|
||||||
getConfig,
|
|
||||||
getDatabaseConfig,
|
getDatabaseConfig,
|
||||||
getServiceConfig,
|
|
||||||
getLoggingConfig,
|
getLoggingConfig,
|
||||||
getProviderConfig,
|
getProviderConfig,
|
||||||
|
getServiceConfig,
|
||||||
|
initializeServiceConfig,
|
||||||
isDevelopment,
|
isDevelopment,
|
||||||
isProduction,
|
isProduction,
|
||||||
isTest,
|
isTest,
|
||||||
resetConfig
|
resetConfig
|
||||||
} from '../src/index';
|
} from '../src/index';
|
||||||
|
|
||||||
const TEST_DIR = join(__dirname, 'real-usage-tests');
|
const TEST_DIR = join(__dirname, 'real-usage-tests');
|
||||||
|
|
@ -44,15 +43,15 @@ describe('Real Usage Scenarios', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should work like real data-service usage', async () => {
|
test('should work like real data-ingestion usage', async () => {
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
// Simulate how data-service would initialize config
|
// Simulate how data-ingestion would initialize config
|
||||||
const config = await initializeServiceConfig();
|
const config = await initializeServiceConfig();
|
||||||
|
|
||||||
// Test typical data-service config access patterns
|
// Test typical data-ingestion config access patterns
|
||||||
expect(config.app.name).toBe('data-service');
|
expect(config.app.name).toBe('data-ingestion');
|
||||||
expect(config.service.port).toBe(3001);
|
expect(config.service.port).toBe(3001);
|
||||||
|
|
||||||
// Test database config access
|
// Test database config access
|
||||||
|
|
@ -108,7 +107,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
test('should handle production environment correctly', async () => {
|
test('should handle production environment correctly', async () => {
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
|
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
resetConfig();
|
resetConfig();
|
||||||
|
|
@ -124,7 +123,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
test('should handle test environment correctly', async () => {
|
test('should handle test environment correctly', async () => {
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
resetConfig();
|
resetConfig();
|
||||||
|
|
@ -144,7 +143,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
process.env.EOD_API_KEY = 'prod-eod-key';
|
process.env.EOD_API_KEY = 'prod-eod-key';
|
||||||
process.env.SERVICE_PORT = '8080';
|
process.env.SERVICE_PORT = '8080';
|
||||||
|
|
||||||
const dataServiceDir = join(TEST_ROOT, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_ROOT, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
resetConfig();
|
resetConfig();
|
||||||
|
|
@ -163,7 +162,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle missing provider configurations gracefully', async () => {
|
test('should handle missing provider configurations gracefully', async () => {
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
const config = await initializeServiceConfig();
|
const config = await initializeServiceConfig();
|
||||||
|
|
@ -178,7 +177,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should support dynamic config access patterns', async () => {
|
test('should support dynamic config access patterns', async () => {
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
const config = await initializeServiceConfig();
|
const config = await initializeServiceConfig();
|
||||||
|
|
@ -187,7 +186,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
const configManager = (await import('../src/index')).getConfigManager();
|
const configManager = (await import('../src/index')).getConfigManager();
|
||||||
|
|
||||||
// Direct path access
|
// Direct path access
|
||||||
expect(configManager.getValue('app.name')).toBe('data-service');
|
expect(configManager.getValue('app.name')).toBe('data-ingestion');
|
||||||
expect(configManager.getValue('service.port')).toBe(3001);
|
expect(configManager.getValue('service.port')).toBe(3001);
|
||||||
|
|
||||||
// Check if paths exist
|
// Check if paths exist
|
||||||
|
|
@ -201,7 +200,7 @@ describe('Real Usage Scenarios', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle config updates at runtime', async () => {
|
test('should handle config updates at runtime', async () => {
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
await initializeServiceConfig();
|
await initializeServiceConfig();
|
||||||
|
|
@ -218,18 +217,18 @@ describe('Real Usage Scenarios', () => {
|
||||||
expect(updatedConfig.service.port).toBe(9999);
|
expect(updatedConfig.service.port).toBe(9999);
|
||||||
|
|
||||||
// Other values should be preserved
|
// Other values should be preserved
|
||||||
expect(updatedConfig.app.name).toBe('data-service');
|
expect(updatedConfig.app.name).toBe('data-ingestion');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should work across multiple service initializations', async () => {
|
test('should work across multiple service initializations', async () => {
|
||||||
// Simulate multiple services in the same process (like tests)
|
// Simulate multiple services in the same process (like tests)
|
||||||
|
|
||||||
// First service
|
// First service
|
||||||
const dataServiceDir = join(TEST_DIR, 'apps', 'data-service');
|
const dataServiceDir = join(TEST_DIR, 'apps', 'data-ingestion');
|
||||||
process.chdir(dataServiceDir);
|
process.chdir(dataServiceDir);
|
||||||
|
|
||||||
let config = await initializeServiceConfig();
|
let config = await initializeServiceConfig();
|
||||||
expect(config.app.name).toBe('data-service');
|
expect(config.app.name).toBe('data-ingestion');
|
||||||
|
|
||||||
// Reset and switch to another service
|
// Reset and switch to another service
|
||||||
resetConfig();
|
resetConfig();
|
||||||
|
|
@ -249,7 +248,7 @@ const TEST_ROOT = TEST_DIR;
|
||||||
function setupRealUsageScenarios() {
|
function setupRealUsageScenarios() {
|
||||||
const scenarios = {
|
const scenarios = {
|
||||||
root: TEST_ROOT,
|
root: TEST_ROOT,
|
||||||
dataService: join(TEST_ROOT, 'apps', 'data-service'),
|
dataService: join(TEST_ROOT, 'apps', 'data-ingestion'),
|
||||||
webApi: join(TEST_ROOT, 'apps', 'web-api'),
|
webApi: join(TEST_ROOT, 'apps', 'web-api'),
|
||||||
cacheLib: join(TEST_ROOT, 'libs', 'cache'),
|
cacheLib: join(TEST_ROOT, 'libs', 'cache'),
|
||||||
};
|
};
|
||||||
|
|
@ -344,10 +343,10 @@ function setupRealUsageScenarios() {
|
||||||
join(scenarios.dataService, 'config', 'development.json'),
|
join(scenarios.dataService, 'config', 'development.json'),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
app: {
|
app: {
|
||||||
name: 'data-service'
|
name: 'data-ingestion'
|
||||||
},
|
},
|
||||||
service: {
|
service: {
|
||||||
name: 'data-service',
|
name: 'data-ingestion',
|
||||||
port: 3001,
|
port: 3001,
|
||||||
workers: 2
|
workers: 2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ bun add @stock-bot/event-bus
|
||||||
import { createEventBus, TradingEventType } from '@stock-bot/event-bus';
|
import { createEventBus, TradingEventType } from '@stock-bot/event-bus';
|
||||||
|
|
||||||
const eventBus = createEventBus({
|
const eventBus = createEventBus({
|
||||||
serviceName: 'data-service',
|
serviceName: 'data-ingestion',
|
||||||
redisConfig: {
|
redisConfig: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 6379,
|
port: 6379,
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
"infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb mongodb",
|
"infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb mongodb",
|
||||||
"dev:full": "npm run infra:up && npm run docker:admin && turbo run dev",
|
"dev:full": "npm run infra:up && npm run docker:admin && turbo run dev",
|
||||||
"dev:clean": "npm run infra:reset && npm run dev:full",
|
"dev:clean": "npm run infra:reset && npm run dev:full",
|
||||||
"proxy": "bun run ./apps/data-service/src/proxy-demo.ts"
|
"proxy": "bun run ./apps/data-ingestion/src/proxy-demo.ts"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"libs/*",
|
"libs/*",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue