name: migrate-service-to-container description: Guides migration from old_code/ to containers/ following SERVICE_MIGRATION_GUIDE.md. Transforms imports to use @coder/shared, adds tenantId to database queries, replaces hardcoded URLs with config, updates routes with auth/tenant enforcement, sets up event publishers/consumers, and transforms service communication. Use when migrating existing services, transforming old code patterns, or refactoring legacy code to new architecture.
Migrate Service to Container
Guides migration from old_code/ to containers/ following SERVICE_MIGRATION_GUIDE.md and 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: SERVICE_MIGRATION_GUIDE.md Step 6, 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: SERVICE_MIGRATION_GUIDE.md Step 7, 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: SERVICE_MIGRATION_GUIDE.md Step 5
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