> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 将 50 个文件从 REST 迁移到 GraphQL

export const UseCaseHero = ({title, description, prompt, category, features, devinUrl, agent, intent, playbookId, type}) => {
  const encodedPrompt = encodeURIComponent(prompt || '');
  const tag = 'docs-use-case-gallery';
  const utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=hero-cta';
  const agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  const devinHref = type === 'schedule' ? 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + (prompt ? '&prompt=' + encodedPrompt : '') : type === 'review' ? 'https://app.devin.ai/review?' + utm : agent === 'ada' ? 'https://app.devin.ai/search?' + utm + '&noSubmit=true' + (prompt ? '&prompt=' + encodedPrompt : '') : devinUrl ? devinUrl.includes('?') ? devinUrl + '&' + utm + agentParams : devinUrl + '?' + utm + agentParams : prompt ? 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encodedPrompt : 'https://app.devin.ai/?' + utm + agentParams;
  const buttonLabel = type === 'schedule' ? 'Schedule in Devin ↗' : type === 'review' ? 'Set Up Devin Review ↗' : agent === 'advanced' ? 'Try in Devin ↗' : agent === 'dana' ? 'Try in Dana ↗' : agent === 'ada' ? 'Try in Ask Devin ↗' : 'Try in Devin ↗';
  const featureList = features ? features.split(',').map(f => f.trim()) : [];
  return <div className="uc-hero">
      <div className="uc-hero-inner">
        <div className="uc-hero-left">
          <h1 className="uc-hero-title">{title}</h1>
          <p className="uc-hero-desc">{description}</p>
          <div>
            <a href={devinHref} target="_blank" rel="noopener noreferrer" className="try-in-devin-btn">
              {buttonLabel}
            </a>
          </div>
        </div>
        <div className="uc-hero-meta">
          <div className="uc-meta-item">
            <span className="uc-meta-label">Author</span>
            <span className="uc-meta-value">Cognition</span>
          </div>
          <div className="uc-meta-item">
            <span className="uc-meta-label">Category</span>
            <span className="uc-meta-value">{category}</span>
          </div>
          {featureList.length > 0 && <div className="uc-meta-item">
              <span className="uc-meta-label">Features</span>
              <span className="uc-meta-value">{featureList.join(', ')}</span>
            </div>}
        </div>
      </div>
    </div>;
};

export const PromptBlock = ({children, type, agent, intent, playbookId}) => {
  var utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=prompt-block';
  var tag = 'docs-use-case-gallery';
  var agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  var label = type === 'schedule' ? 'Schedule in Devin' : type === 'playbook' ? 'Create Playbook' : type === 'knowledge' ? 'Add to Knowledge' : agent === 'advanced' ? 'Try in Devin' : agent === 'dana' ? 'Try in Dana' : agent === 'ada' ? 'Try in Ask Devin' : 'Try in Devin';
  var buildUrl = function (text) {
    var encoded = encodeURIComponent(text);
    if (type === 'schedule') return 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + '&prompt=' + encoded;
    if (type === 'playbook') return 'https://app.devin.ai/settings/playbooks/create?' + utm + '&body=' + encoded;
    if (type === 'knowledge') return 'https://app.devin.ai/knowledge?' + utm + '&body=' + encoded;
    if (agent === 'ada') return 'https://app.devin.ai/search?' + utm + '&noSubmit=true&prompt=' + encoded;
    return 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encoded;
  };
  const ref = React.useRef(null);
  const [href, setHref] = React.useState('#');
  React.useEffect(() => {
    if (!ref.current) return;
    var codeEl = ref.current.querySelector('pre code');
    if (codeEl) {
      var text = codeEl.textContent.trim();
      if (text) setHref(buildUrl(text));
    }
    var header = ref.current.querySelector('[data-component-part="code-block-header"]');
    if (header && !header.querySelector('.prompt-block-devin-link')) {
      var link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.className = 'prompt-block-devin-link';
      link.style.cssText = 'display:inline-flex;align-items:center;gap:6px;text-decoration:none;color:#fff;font-size:11px;font-weight:500;padding:4px 10px;border-radius:6px;white-space:nowrap;background:#317CFF;transition:background 0.2s;margin-left:8px;';
      link.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> ' + label;
      link.onmouseenter = function () {
        link.style.background = '#2968D9';
      };
      link.onmouseleave = function () {
        link.style.background = '#317CFF';
      };
      header.appendChild(link);
    }
    var existingLink = ref.current.querySelector('.prompt-block-devin-link');
    if (existingLink && href !== '#') existingLink.href = href;
  });
  return <div className="prompt-block" ref={ref}>{children}</div>;
};

<UseCaseHero title="将 50 个文件从 REST 迁移到 GraphQL" description="规划一次包含 50 个文件的从 REST 到 GraphQL 的迁移，将其拆分为互不冲突的工作包，并通过托管的 Devin 一次性运行所有工作包。" prompt="分析我们的代码库，查找所有使用旧版 REST 客户端的文件。将它们分组为彼此独立且不会产生冲突的工作包，然后为每个工作包启动并行的 Devin 会话，将其迁移到新的 GraphQL 客户端。" category="迁移" features="高级, Playbooks" agent="advanced" intent="batch" />

<div className="uc-detail-wrapper">
  <Tip>不想手动配置？将本页链接粘贴到 Devin 会话中，让它为你完成所有设置。</Tip>

  <Steps>
    <Step title="使用 Ask Devin 确定迁移范围">
      你有 50 个文件从 `src/lib/restClient.ts` 导入，需要迁移到新的 `graphqlClient`。在将工作拆分为并行任务之前，你需要了解这些文件之间是如何关联的。使用 [Ask Devin](/zh/work-with-devin/ask-devin) 来梳理迁移的范围——哪些文件导入了旧版客户端、它们按业务域如何聚类，以及高风险的耦合关系分布在哪里。Devin 在底层使用了 [DeepWiki](/zh/work-with-devin/deepwiki) 和语义搜索，因此它可以基于你的实际代码来回答这些问题。

      <Tip>Ask Devin 需要你的代码仓库已完成[索引](/zh/onboard-devin/index-repo)。前往 [**Settings > Repositories**](https://app.devin.ai/settings/repositories?utm_source=docs\&utm_medium=use-case-gallery) 查看索引状态或为新仓库建立索引。</Tip>

      打开 [Ask Devin](https://app.devin.ai/search?utm_source=docs\&utm_medium=use-case-gallery) 并提出以下问题：

      <PromptBlock agent="ada">
        ```txt Scope the REST-to-GraphQL migration theme={null}
        How many files import from src/lib/restClient.ts? Group them by
        domain (user management, billing, analytics, notifications, auth,
        admin, search, onboarding, etc.) and flag any files that share
        state or have tight coupling across domains.
        Note which files have existing test coverage.
        ```
      </PromptBlock>

      Ask Devin 会返回类似下面这样的拆解结果：

      ```
      50 个文件在 8 个领域中引用了 restClient：

        User Management  （7 个文件）— 独立，完整测试覆盖
        Billing          （9 个文件）— 独立，6/9 已测试
        Analytics        （5 个文件）— 独立，3/5 已测试
        Auth             （6 个文件）— 共享中间件，完整测试覆盖
        Notifications    （8 个文件）— 独立，5/8 已测试
        Admin            （5 个文件）— 依赖 Auth 中间件
        Search           （4 个文件）— 独立，2/4 已测试
        Onboarding       （6 个文件）— 独立，4/6 已测试

      耦合说明：Admin 从 Auth 引入 requireAuth。请先迁移
      Auth，再迁移 Admin。其他所有领域均相互独立。
      ```

      这可以帮助你判断是否适合并行化处理。如果大多数文件在不同域之间高度耦合，那么按顺序迁移会更安全。在这里，8 个域中有 6 个是完全独立的——你可以并行迁移它们。
    </Step>

    <Step title="创建迁移 playbook">
      每个并行会话都应遵循相同的迁移模式，这样生成的 PR 才能保持一致，并且便于审查。创建一个[运行手册](/zh/product-guides/creating-playbooks)，明确规定每个文件应如何迁移。

      前往 [**Settings > Playbooks > Create Playbook**](https://app.devin.ai/settings/playbooks/create?utm_source=docs\&utm_medium=use-case-gallery) 并定义以下步骤：

      <PromptBlock type="playbook">
        ```txt REST to GraphQL Migration theme={null}
        For each file assigned to you:

        1. Replace `import { restClient } from 'src/lib/restClient'`
           with `import { graphqlClient } from 'src/lib/graphqlClient'`
        2. Rewrite each API call to use the corresponding GraphQL
           query or mutation defined in src/graphql/operations/
        3. Update error handling from HTTP status codes to GraphQL
           error types (see src/lib/graphqlClient.ts for the error map)
        4. Update the corresponding test file:
           - Replace REST endpoint mocks with GraphQL mocks using msw
           - Assert on the GraphQL operation name, not the URL
        5. Run `npm run typecheck && npm test -- --related` to verify
        6. Open a PR titled: "migrate: [domain] REST → GraphQL"
        ```
      </PromptBlock>

      或者让 Devin 为你自动生成运行手册——描述你的迁移模式，Devin 会生成一份完整的运行手册：

      <PromptBlock agent="advanced">
        ```txt Generate a REST-to-GraphQL migration playbook theme={null}
        Create a playbook for migrating files from our REST client
        (src/lib/restClient.ts) to our new GraphQL client
        (src/lib/graphqlClient.ts). Look at src/graphql/operations/ for
        the available queries and mutations. Include steps for updating
        imports, rewriting API calls, converting error handling from HTTP
        status codes to GraphQL error types, updating tests to use msw
        GraphQL mocks, and running typecheck + tests.
        ```
      </PromptBlock>

      在你的编排提示中引用这个运行手册，可以确保所有并行会话提交的 PR 看起来都像出自同一位开发者之手。
    </Step>

    <Step title="使用 Devin 启动并行会话">
      从 [Devin 首页](https://app.devin.ai/?utm_source=docs\&utm_medium=use-case-gallery) 打开一个新的 Devin 会话，并向它提供编排提示。Devin 会分析你的代码库依赖关系图，创建独立的工作包，并为每个工作包启动一个会话——全部并行运行。

      <PromptBlock agent="advanced" intent="batch">
        ```txt 将 50 个文件的 REST 向 GraphQL 迁移并行化 theme={null}
        分析我们的代码库，找到每一个从
        src/lib/restClient.ts 导入的文件。根据这些
        文件之间的依赖关系图，识别可以独立迁移的分组——
        任意两个分组都不应修改同一个文件，也不应共享可变状态。

        对于每个独立分组，使用
        "REST to GraphQL Migration" playbook 启动一个并行 Devin 会话。每个会话应当：
        - 仅迁移分配给它的文件
        - 在其作用域内运行类型检查和测试
        - 创建一个单独的拉取请求（PR）

        在启动会话之前，把拟议的分组展示给我，以便我
        审批。包含每个分组的文件数量和预估复杂度。
        ```
      </PromptBlock>

      Devin 会在启动任何操作之前，先给出一个分组方案供你审批：

      ```
      建议的工作包（8 个分组，共 50 个文件）：

      分组 1 — 用户管理（7 个文件，复杂度：M）
        src/services/UserService.ts, src/pages/Profile.tsx,
        src/pages/Settings.tsx, src/hooks/useCurrentUser.ts,
        src/pages/UserDirectory.tsx, src/services/AvatarService.ts,
        src/components/UserCard.tsx

      分组 2 — 账单（9 个文件，复杂度：L）
        src/services/BillingService.ts, src/services/InvoiceService.ts,
        src/services/SubscriptionService.ts, src/pages/Checkout.tsx,
        src/pages/PlanSelector.tsx, src/pages/InvoiceHistory.tsx,
        src/components/PaymentForm.tsx, src/hooks/useSubscription.ts,
        src/components/PricingTable.tsx

      分组 3 — 数据分析（5 个文件，复杂度：S）
        src/services/AnalyticsService.ts, src/pages/Dashboard.tsx,
        src/components/MetricsCard.tsx, src/hooks/useMetrics.ts,
        src/components/ChartPanel.tsx

      分组 4 — 认证（6 个文件，复杂度：L）⚠ 优先迁移
        src/services/AuthService.ts, src/middleware/requireAuth.ts,
        src/middleware/refreshToken.ts, src/pages/Login.tsx,
        src/pages/Signup.tsx, src/services/SessionService.ts

      分组 5 — 通知（8 个文件，复杂度：M）
        src/services/NotificationService.ts, src/services/EmailService.ts,
        src/services/PushService.ts, src/pages/NotificationPreferences.tsx,
        src/components/NotificationBell.tsx, src/hooks/useNotifications.ts,
        src/components/NotificationToast.tsx, src/services/WebhookService.ts

      分组 6 — 管理员（5 个文件，复杂度：M）⚠ 依赖认证分组
        src/pages/AdminDashboard.tsx, src/pages/AdminUsers.tsx,
        src/services/AdminService.ts, src/components/AdminSidebar.tsx,
        src/middleware/requireAdmin.ts

      分组 7 — 搜索（4 个文件，复杂度：S）
        src/services/SearchService.ts, src/pages/SearchResults.tsx,
        src/components/SearchBar.tsx, src/hooks/useSearch.ts

      分组 8 — 新用户引导（6 个文件，复杂度：M）
        src/services/OnboardingService.ts, src/pages/Welcome.tsx,
        src/pages/SetupWizard.tsx, src/components/ProgressTracker.tsx,
        src/hooks/useOnboardingState.ts, src/services/ChecklistService.ts

      依赖说明：分组 6（管理员）引用了分组 4（认证）的中间件。将优先启动认证分组，待认证分组的 PR 合并后再启动管理员分组。
      其余 6 个分组立即并行启动。

      现在启动 6 个并行会话 + 2 个顺序会话？(y/n)
      ```

      批准分组，并一次性启动这六个会话。先运行 `Auth`，在 `Auth` 合并后再运行 `Admin`。
    </Step>

    <Step title="审查并合并结果">
      每个会话都会打开自己对应的 PR。由于这些包彼此独立，你可以按任意顺序进行审查和合并——但要先合并 Auth，因为 Admin 依赖它；并且在每次合并后都运行完整 CI，以捕获任何意料之外的交互问题。

      在所有 8 个迁移 PR 都合并完成后，开启一个后续会话来清理死代码：

      <PromptBlock>
        ```txt Post-migration cleanup theme={null}
        所有从 REST 到 GraphQL 的迁移 PR 都已合并。扫描代码库以查找：
        1. 任何仍然引入 src/lib/restClient.ts 的地方
        2. 仅被 REST client 使用的共享工具函数中的死代码
        3. 可以移除的、特定于 REST 的类型或接口

        清理这些内容，如果已经没有任何地方引用 src/lib/restClient.ts，
        就将该文件彻底删除。
        ```
      </PromptBlock>
    </Step>
  </Steps>
</div>
