遗留系统现代化

在不停机前提下用防腐层、BFF 与绞杀榕逐步替换单体或陈旧栈;Agent 输出应含边界、数据同步策略与业务优先级排序。

SKILL 先盘点:哪些能力创收或合规关键、哪些可延后;技术债按风险与频率二维排序。对外契约用适配器隔离,避免新服务直接穿透旧表结构。

数据双写、回填与对账是常见坑:须写明一致性窗口、纠偏任务与回滚开关。事件驱动迁移时定义 schema 版本与死信处理。

组织层面:每个绞杀榕切片应有负责人、里程碑与「仍可回退到旧路径」的条件;Agent 可协助起草 ADR 与沟通话术,但不能替业务拍板范围。

绞杀榕主流程(skill-flow-block)

  [ 入口:Facade / API 网关 / BFF 按路由切新旧 ]
                    │
                    ▼
         [ 新切片:独立部署、契约测试、防腐层读旧 ]
                    │
                    ▼
    [ 流量渐进:按租户 / 功能 / 百分比 切到新实现 ]
                    │
                    ▼
         [ 收口:删重复逻辑、禁止新代码直连旧库 ]
                    │
                    ▼
  [ 终态:旧域只读或下线;数据经迁移或同步到新边界 ]

Nginx upstream 切换配置(按权重渐进迁移流量):

# nginx.conf — 绞杀榕流量切换
upstream legacy_orders {
    server legacy-app:8080;
}

upstream new_orders {
    server new-orders-service:3000;
}

# 按百分比分流:90% 旧服务,10% 新服务
upstream orders_split {
    server legacy-app:8080 weight=9;
    server new-orders-service:3000 weight=1;
}

server {
    listen 80;

    # 阶段1:特定租户先切到新服务(金丝雀)
    location /api/orders {
        # 通过 Cookie 或 Header 控制灰度
        set $upstream_orders "legacy_orders";
        if ($http_x_canary = "true") {
            set $upstream_orders "new_orders";
        }
        proxy_pass http://$upstream_orders;
        proxy_set_header Host $host;
    }

    # 阶段2:按百分比分流(替换为 orders_split)
    # location /api/orders {
    #     proxy_pass http://orders_split;
    # }

    # 阶段3:全量切新服务(替换为 new_orders)
    # location /api/orders {
    #     proxy_pass http://new_orders;
    # }
}

每一环在 SKILL 中写清「谁拥有路由开关」「回切到旧路径的条件」与「本环完成的验收指标」,避免切片悬空无人收尾。

边界与防腐层

  • 按业务能力划界,而非按现有包名或数据库表名照搬。
  • 对外暴露稳定契约;对内通过适配器翻译旧模型,禁止新域泄漏旧表字段语义。
  • 契约测试覆盖 happy path 与错误码映射,防止静默降级。

防腐层(Anti-Corruption Layer)接口代码示例:

// 旧系统返回的用户模型(legacy format)
// { usr_id: "123", usr_nm: "张三", usr_email: "a@b.com", create_dt: "20240101" }

// 新系统的领域模型
interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

// 防腐层 Adapter:翻译旧 API 响应为新领域模型
class LegacyUserAdapter {
  private legacyClient: LegacyApiClient;

  async getUserById(id: string): Promise<User> {
    const raw = await this.legacyClient.get(`/usr?usr_id=${id}`);
    return this.translate(raw);
  }

  async searchUsers(email: string): Promise<User[]> {
    const raws = await this.legacyClient.get(`/usr/search?email=${email}`);
    return raws.map(this.translate);
  }

  private translate(raw: any): User {
    return {
      id:        raw.usr_id,
      name:      raw.usr_nm,
      email:     raw.usr_email,
      // 旧系统日期格式 "20240101" → Date 对象
      createdAt: new Date(
        raw.create_dt.slice(0,4) + "-" +
        raw.create_dt.slice(4,6) + "-" +
        raw.create_dt.slice(6,8)
      ),
    };
  }
}

// 新服务只依赖 LegacyUserAdapter,不直接调用旧 API
// 旧 API 变更时,只需改 Adapter,新服务不受影响

数据同步与对账

  • 双写须定义一致性窗口、幂等与冲突策略;回填 job 可限速、可重试、可暂停。
  • 对账任务与纠偏 runbook 写进计划;回滚开关与「只读旧主」等熔断条件明确。
  • 事件/schema 版本化;死信队列与重放策略在 SKILL 中单列一节。

双写中间件实现(同时写新旧系统)与数据对账脚本框架:

// 双写中间件:同时写旧系统和新系统
class DualWriteOrderRepository {
  constructor(
    private legacyRepo: LegacyOrderRepository,
    private newRepo: NewOrderRepository,
    private featureFlag: FeatureFlags,
  ) {}

  async save(order: Order): Promise<void> {
    // 始终写旧系统(主)
    await this.legacyRepo.save(order);

    // 按特性开关决定是否同时写新系统
    if (await this.featureFlag.isEnabled("dual-write-orders")) {
      try {
        await this.newRepo.save(order);
      } catch (err) {
        // 新系统失败不影响主流程,但要上报告警
        metrics.increment("dual_write.new_system.error");
        logger.error("新系统写入失败", { orderId: order.id, err });
      }
    }
  }
}

// 数据对账脚本框架(比较新旧系统数据一致性)
async function reconcile(batchSize = 100, offsetDate: Date) {
  let mismatches = 0, checked = 0;

  for await (const legacyBatch of legacyRepo.streamSince(offsetDate, batchSize)) {
    const ids = legacyBatch.map(o => o.id);
    const newBatch = await newRepo.findByIds(ids);
    const newMap = new Map(newBatch.map(o => [o.id, o]));

    for (const legacyOrder of legacyBatch) {
      checked++;
      const newOrder = newMap.get(legacyOrder.id);
      if (!newOrder) {
        logger.warn("新系统缺失记录", { id: legacyOrder.id });
        mismatches++;
        continue;
      }
      if (legacyOrder.amount !== newOrder.amount ||
          legacyOrder.status !== newOrder.status) {
        logger.warn("数据不一致", { id: legacyOrder.id, legacy: legacyOrder, new: newOrder });
        mismatches++;
      }
    }
  }
  console.log(`对账完成:检查 ${checked} 条,不一致 ${mismatches} 条`);
  return { checked, mismatches };
}

组织与切片

  • 每切片:负责人、里程碑、依赖团队与沟通节奏(评审 / 演示)。
  • 明确「仍可回退到旧路径」的触发条件与操作步骤。
  • ADR 记录重大边界与路由决策;Agent 可起草初稿,范围由业务拍板。

原则与禁忌

  • 禁止大爆炸重写,除非有经批准的停机窗口、备份与回滚演练。
  • 可观测性先行:新旧路径均具备指标、日志与分布式 trace。
  • 切片内部仍坚持小步提交与安全重构,避免「大 PR 换一切」。

SKILL 片段

---
name: legacy-modernization
description: 绞杀榕模式、防腐层与数据双写对账
---
# 现代化步骤
1. 按业务能力划界(非表名 / 包名)并排优先级
2. 设计 Nginx upstream 或 API Gateway 路由切换方案
3. 实现防腐层 Adapter,翻译旧 API 响应为新领域模型

# 流量渐进策略
- 阶段1:特定租户 / Header 灰度(X-Canary: true)
- 阶段2:按百分比分流(Nginx weight 或特性开关)
- 阶段3:全量切新服务,旧路由保留 30 天备回滚
- 每阶段写清回切条件与操作步骤

# 数据同步
- 双写中间件:旧系统为主,新系统失败不阻断主流程但需告警
- 回填 job:可限速(500 条/秒)、可暂停、可重试
- 幂等写入:以业务 ID 为主键,重复执行无副作用

# 数据对账
- 每日对账:比对新旧系统记录 ID / 金额 / 状态
- 不一致上报告警(Slack / PagerDuty)
- 纠偏 runbook:不一致 > 0.1% 时暂停双写并人工介入

# 原则
- 禁止大爆炸重写(无停机窗口时)
- 可观测性先行:新旧路径均具备指标和 trace
- 每切片有负责人、里程碑与验收指标

返回技能库 更多技能入口