AgentSkillsCN

migrate-service-to-container

根据 ModuleImplementationGuide.md 第 9.6 节的指导,设置 RabbitMQ 定时批量任务。在工作流编排器中创建 BatchJobScheduler(node-cron、publish workflow.job.trigger),并在工作器容器中创建 BatchJobWorker(consume bi_batch_jobs、run job、publish workflow.job.completed/failed)。适用于添加风险聚类、账户健康、行业基准、风险快照补全,或类似由 Cron 驱动的任务时使用。

SKILL.md
--- frontmatter
name: migrate-service-to-container
description: Guides migration of existing services to containers/ following ModuleImplementationGuide.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 legacy code patterns, or refactoring to container architecture.

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:

bash
mkdir -p containers/<service-name>/{config,src/{config,routes,services,types,utils,events,middleware},tests/{unit,integration}}

Step 2: Transform Imports

Old Pattern:

typescript
import { config } from '../config/env.js';
import { CosmosClient } from '@azure/cosmos';

New Pattern:

typescript
import { CosmosDBClient } from '@coder/shared';
import { loadConfig } from '../config';

Reference: ModuleImplementationGuide.md Section 5 (Dependency Rules)

Step 3: Transform Database Queries

Old Pattern:

typescript
async getData(id: string) {
  const query = `SELECT * FROM c WHERE c.id = @id`;
  // ❌ No tenantId
}

New Pattern:

typescript
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:

typescript
const response = await fetch('http://localhost:3021/api/users/123');

New Pattern:

typescript
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:

typescript
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:

typescript
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:

typescript
throw new Error('Something went wrong');

New Pattern:

typescript
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:

typescript
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:

typescript
const service = new MyService(cosmosClient, redis, monitoring);

New Pattern:

typescript
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