Migrate Service to Container
Guides migration of existing services to containers/ following ModuleImplementationGuide.md standards.
Pre-Migration Analysis
Before migrating, analyze:
- • Dependencies: What services does this depend on?
- • Database: What Cosmos DB containers does it use?
- • Events: What events does it publish/consume?
- • External APIs: What external services does it call?
- • Configuration: What configuration values does it need?
- • Routes: What API endpoints does it expose?
- • Business Logic: What are the core services/classes?
Migration Steps
Step 1: Create Module Structure
Use create-container-module skill or manually create:
mkdir -p containers/<service-name>/{config,src/{config,routes,services,types,utils,events,middleware},tests/{unit,integration}}
Step 2: Transform Imports
Old Pattern:
import { config } from '../config/env.js';
import { CosmosClient } from '@azure/cosmos';
New Pattern:
import { CosmosDBClient } from '@coder/shared';
import { loadConfig } from '../config';
Reference: ModuleImplementationGuide.md Section 5 (Dependency Rules)
Step 3: Transform Database Queries
Old Pattern:
async getData(id: string) {
const query = `SELECT * FROM c WHERE c.id = @id`;
// ❌ No tenantId
}
New Pattern:
async getData(tenantId: string, id: string) {
const container = this.db.getContainer('service_data');
const query = `SELECT * FROM c WHERE c.tenantId = @tenantId AND c.id = @id`;
const parameters = [
{ name: '@tenantId', value: tenantId },
{ name: '@id', value: id }
];
// ✅ tenantId required, uses shared client
}
Reference: ModuleImplementationGuide.md Section 8
Step 4: Replace Hardcoded URLs
Old Pattern:
const response = await fetch('http://localhost:3021/api/users/123');
New Pattern:
import { ServiceClient } from '@coder/shared';
const client = new ServiceClient({
baseUrl: config.services.auth.url, // From config
timeout: 5000,
});
const response = await client.get('/api/v1/users/123', {
headers: {
'X-Tenant-ID': tenantId,
'Authorization': `Bearer ${serviceToken}`,
},
});
Reference: ModuleImplementationGuide.md Section 5.3
Step 5: Transform Routes
Old Pattern:
fastify.get('/api/my-service/data', async (request, reply) => {
const service = new MyService();
const data = await service.getData(request.params.id);
return reply.send(data);
});
New Pattern:
import { authenticateRequest, tenantEnforcementMiddleware } from '@coder/shared';
fastify.get<{ Params: { id: string } }>(
'/api/v1/data/:id',
{
preHandler: [authenticateRequest(), tenantEnforcementMiddleware()],
},
async (request, reply) => {
// ✅ tenantId available from tenantEnforcementMiddleware
const tenantId = request.user!.tenantId;
const data = await service.getData(tenantId, request.params.id);
return reply.send({ data });
}
);
Reference: ModuleImplementationGuide.md Section 7 (Routes)
Step 6: Update Error Handling
Old Pattern:
throw new Error('Something went wrong');
New Pattern:
import { AppError } from '@coder/shared';
throw new AppError('Something went wrong', 400, 'BAD_REQUEST');
Reference: ModuleImplementationGuide.md Section 10 (Error Handling)
Step 7: Set Up Event Publishing
New Pattern:
import { EventPublisher } from '@coder/shared';
const publisher = new EventPublisher(config.rabbitmq);
await publisher.publish('service.resource.created', {
id: resource.id,
tenantId: tenantId,
timestamp: new Date().toISOString(),
});
Reference: ModuleImplementationGuide.md Section 9 (Event-Driven Communication)
Step 8: Transform Service Initialization
Old Pattern:
const service = new MyService(cosmosClient, redis, monitoring);
New Pattern:
import { getDatabaseClient, getCacheClient } from '@coder/shared';
const db = getDatabaseClient();
const cache = getCacheClient();
const service = new MyService(db, cache);
Migration Checklist
Pre-Migration
- • Analyze dependencies
- • Map database containers
- • Identify events (published/consumed)
- • List API endpoints
- • Document configuration needs
Code Migration
- • Create module directory structure
- • Copy service files
- • Transform imports (use @coder/shared)
- • Add tenantId to all database queries
- • Replace hardcoded URLs with config
- • Transform routes (add auth, tenant enforcement)
- • Update error handling (use AppError)
- • Add event publishing/consuming
Configuration
- • Create config/default.yaml
- • Create config/schema.json
- • Create config/index.ts loader
- • Add environment variable documentation
Validation
- • No hardcoded ports/URLs
- • All queries include tenantId
- • Service-to-service auth implemented
- • Follows ModuleImplementationGuide.md