第4章 分层架构

3 阅读10分钟

第4章 分层架构

引言

在软件工程中,分层架构是一种经典的设计模式,它将系统划分为多个层次,每个层次负责特定的职责。Claude Code 作为一个复杂的 AI 编程助手系统,采用了清晰的五层架构设计。这种设计不仅让系统易于理解和维护,更重要的是为"对话即编程"范式提供了坚实的技术基础。

本章将深入剖析 Claude Code 的分层架构,从 CLI 层到基础设施层,逐层揭示其设计思想和实现细节。我们将看到,良好的分层架构如何让复杂的系统变得优雅而强大。

概念讲解:五层架构全景

Claude Code 的五层架构从上到下依次为:

  1. CLI 层:命令行界面,负责用户交互
  2. 查询引擎层:核心业务逻辑,管理对话流程
  3. 工具命令层:工具注册和执行
  4. 服务层:各种业务服务(API、MCP、文件服务等)
  5. 基础设施层:底层支撑(状态管理、费用追踪、日志等)

架构图

┌─────────────────────────────────────────┐
│           CLI 层 (React + Ink)          │
│  - 命令解析  - UI 渲染  - 用户输入处理   │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│        查询引擎层 (QueryEngine)          │
│  - 消息提交  - 状态机  - 流式响应       │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│       工具命令层 (Tools + Commands)     │
│  - 工具注册  - 命令解析  - 权限管理     │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│          服务层 (Services)              │
│  - API 服务  - MCP 服务  - 文件服务     │
└────────────────┬────────────────────────┘
                 │
┌────────────────▼────────────────────────┐
│       基础设施层 (Infrastructure)        │
│  - 状态管理  - 费用追踪  - 日志系统     │
└─────────────────────────────────────────┘

这种分层设计的核心优势在于:

  1. 职责清晰:每层专注于自己的职责,降低耦合
  2. 易于测试:可以独立测试每一层
  3. 灵活扩展:新增功能只需修改相关层次
  4. 团队协作:不同团队可以负责不同层次

源码分析:CLI 层

CLI 层是用户与系统交互的入口。从项目结构可以看到,CLI 相关的代码包括:

  • src/commands.ts:命令定义
  • src/dialogLaunchers.tsx:对话启动器
  • src/replLauncher.tsx:REPL 启动器
  • src/interactiveHelpers.tsx:交互辅助组件

CLI 层使用了 Commander.js 作为命令行框架,React + Ink 作为终端 UI 框架。这种组合让 CLI 既强大又美观。

从 QueryEngine.ts 的导入可以看到 CLI 层与查询引擎层的连接:

import type { Command } from './commands.js'
import { getSlashCommandToolSkills } from './commands.js'

Command 类型定义了命令的结构,getSlashCommandToolSkills 函数将斜杠命令转换为工具,使得 AI 可以理解并执行这些命令。

源码分析:查询引擎层

查询引擎层是整个系统的核心,负责管理"对话即编程"的流程。QueryEngine.ts 是这一层的主要实现。

从导入部分可以看到查询引擎层依赖了大量其他层次:

import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs'
import { randomUUID } from 'crypto'
import last from 'lodash-es/last.js'
import {
  getSessionId,
  isSessionPersistenceDisabled,
} from 'src/bootstrap/state.js'
import { accumulateUsage, updateUsage } from 'src/services/api/claude.js'
import { getModelUsage, getTotalAPIDuration, getTotalCost } from './cost-tracker.js'
import { query } from './query.js'
import type { Tools, type ToolUseContext, toolMatchesName } from './Tool.js'

查询引擎层的核心职责包括:

  1. 消息管理:接收用户消息,管理对话历史
  2. 上下文收集:调用 getSystemContext()getUserContext()
  3. 查询执行:调用 query() 函数进入状态机循环
  4. 结果处理:将 AI 响应转换为 UI 可显示的格式

ToolUseContext 的贯穿作用

ToolUseContext 是一个关键类型,它贯穿了查询引擎层、工具命令层和服务层:

export type ToolUseContext = {
  // 从 Tool.ts 中可以看到完整的定义
  // 包括:消息历史、文件状态、权限信息、任务状态等
}

从 query.ts 的导入可以看到:

import { findToolByName, type ToolUseContext } from './Tool.js'

ToolUseContext 携带了执行工具所需的所有上下文信息,包括:

  • 当前对话的消息历史
  • 文件系统状态
  • 权限和配置信息
  • 任务状态
  • 会话信息

这种设计让每一层都能访问到必要的上下文,同时保持了类型安全。

源码分析:工具命令层

工具命令层负责工具的注册、管理和执行。tools.ts 是这一层的主要文件。

从导入可以看到工具命令层的结构:

import { toolMatchesName, type Tool, type Tools } from './Tool.js'
import { AgentTool } from './tools/AgentTool/AgentTool.js'
import { SkillTool } from './tools/SkillTool/SkillTool.js'
import { BashTool } from './tools/BashTool/BashTool.js'
import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
import { FileReadTool } from './tools/FileReadTool/FileReadTool.js'
import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool.js'
import { GlobTool } from './tools/GlobTool/GlobTool.js'

工具命令层的核心设计包括:

  1. 工具注册:每个工具都是一个独立的模块,通过统一的接口注册
  2. 工具匹配toolMatchesName 函数用于匹配工具名称
  3. 工具执行:通过 runTools 函数执行工具调用

从 query.ts 的导入可以看到工具命令层与服务层的交互:

import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js'
import { runTools } from './services/tools/toolOrchestration.js'

StreamingToolExecutor 负责流式执行工具,runTools 负责工具编排。

工具类型定义

Tool.ts 定义了工具的核心类型:

export type ToolInputJSONSchema = {
  [x: string]: unknown
  type: 'object'
  properties?: {
    [x: string]: unknown
  }
}

这是工具输入的 JSON Schema 定义,确保了类型安全。每个工具都必须明确定义其输入参数的结构。

源码分析:服务层

服务层提供了各种业务服务,包括 API 服务、MCP 服务、文件服务等。从 query.ts 的导入可以看到服务层的组成:

import {
  calculateTokenWarningState,
  isAutoCompactEnabled,
  type AutoCompactTrackingState,
} from './services/compact/autoCompact.js'
import { buildPostCompactMessages } from './services/compact/compact.js'
import {
  logEvent,
  type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
} from 'src/services/analytics/index.js'
import { ImageSizeError } from './utils/imageValidation.js'
import { ImageResizeError } from './utils/imageResizer.js'

服务层的核心服务包括:

  1. API 服务:与 Anthropic API 通信
  2. 压缩服务:管理上下文压缩
  3. 分析服务:日志和分析
  4. 文件服务:文件操作
  5. MCP 服务:Model Context Protocol 集成

服务层的设计特点:

  • 模块化:每个服务都是独立的模块
  • 可测试:服务层可以独立测试
  • 可扩展:新增服务只需添加新模块

源码分析:基础设施层

基础设施层提供了底层支撑功能,包括状态管理、费用追踪、日志系统等。cost-tracker.ts 和 Task.ts 是这一层的重要文件。

费用追踪设计

cost-tracker.ts 展示了基础设施层的设计精髓:

import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import chalk from 'chalk'
import {
  addToTotalCostState,
  addToTotalLinesChanged,
  getCostCounter,
  getModelUsage,
  getSdkBetas,
  getSessionId,
  getTokenCounter,
  getTotalAPIDuration,
  getTotalAPIDurationWithoutRetries,
  getTotalCacheCreationInputTokens,
  getTotalCacheReadInputTokens,
  getTotalCostUSD,
  getTotalDuration,
  getTotalLinesAdded,
  getTotalLinesRemoved,
  getTotalOutputTokens,
  getTotalToolDuration,
  getTotalWebSearchRequests,
  getUsageForModel,
  hasUnknownModelCost,
  resetCostState,
  resetStateForTests,
  setCostStateForRestore,
  setHasUnknownModelCost,
} from './bootstrap/state.js'

费用追踪的核心功能包括:

  1. 费用计算:根据 token 使用量计算费用
  2. 费用累积:累积整个会话的费用
  3. 费用展示:格式化展示费用信息
  4. 费用持久化:将费用保存到项目配置

费用追踪的关键函数:

export function addToTotalSessionCost(
  cost: number,
  usage: Usage,
  model: string,
): number {
  const modelUsage = addToTotalModelUsage(cost, usage, model)
  addToTotalCostState(cost, modelUsage, model)

  const attrs =
    isFastModeEnabled() && usage.speed === 'fast'
      ? { model, speed: 'fast' }
      : { model }

  getCostCounter()?.add(cost, attrs)
  getTokenCounter()?.add(usage.input_tokens, { ...attrs, type: 'input' })
  getTokenCounter()?.add(usage.output_tokens, { ...attrs, type: 'output' })
  getTokenCounter()?.add(usage.cache_read_input_tokens ?? 0, {
    ...attrs,
    type: 'cacheRead',
  })
  getTokenCounter()?.add(usage.cache_creation_input_tokens ?? 0, {
    ...attrs,
    type: 'cacheCreation',
  })

  let totalCost = cost
  for (const advisorUsage of getAdvisorUsage(usage)) {
    const advisorCost = calculateUSDCost(advisorUsage.model, advisorUsage)
    logEvent('tengu_advisor_tool_token_usage', {
      advisor_model:
        advisorUsage.model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      input_tokens: advisorUsage.input_tokens,
      output_tokens: advisorUsage.output_tokens,
      cache_read_input_tokens: advisorUsage.cache_read_input_tokens ?? 0,
      cache_creation_input_tokens:
        advisorUsage.cache_creation_input_tokens ?? 0,
      cost_usd_micros: Math.round(advisorCost * 1_000_000),
    })
    totalCost += addToTotalSessionCost(
      advisorCost,
      advisorUsage,
      advisorUsage.model,
    )
  }
  return totalCost
}

这个函数展示了基础设施层的设计特点:

  • 精确追踪:追踪所有类型的 token 使用
  • 多模型支持:支持多个模型的费用追踪
  • 顾问工具:支持顾问工具的费用追踪
  • 事件记录:记录分析事件

任务类型和状态机设计

Task.ts 定义了任务系统的核心类型:

export type TaskType =
  | 'local_bash'
  | 'local_agent'
  | 'remote_agent'
  | 'in_process_teammate'
  | 'local_workflow'
  | 'monitor_mcp'
  | 'dream'

export type TaskStatus =
  | 'pending'
  | 'running'
  | 'completed'
  | 'failed'
  | 'killed'

任务类型涵盖了系统中的各种任务:

  • local_bash:本地 bash 命令
  • local_agent:本地 agent
  • remote_agent:远程 agent
  • in_process_teammate:进程内队友
  • local_workflow:本地工作流
  • monitor_mcp:MCP 监控
  • dream:梦境任务

任务状态定义了任务的生命周期:

  • pending:等待中
  • running:运行中
  • completed:已完成
  • failed:失败
  • killed:被终止

任务状态机的关键函数:

export function isTerminalTaskStatus(status: TaskStatus): boolean {
  return status === 'completed' || status === 'failed' || status === 'killed'
}

这个函数判断任务是否处于终止状态,用于防止向已完成的任务注入消息。

任务 ID 的生成:

export function generateTaskId(type: TaskType): string {
  const prefix = getTaskIdPrefix(type)
  const bytes = randomBytes(8)
  let id = prefix
  for (let i = 0; i < 8; i++) {
    id += TASK_ID_ALPHABET[bytes[i]! % TASK_ID_ALPHABET.length]
  }
  return id
}

任务 ID 的生成展示了基础设施层的设计细节:

  • 类型前缀:每个任务类型有独特的前缀
  • 随机生成:使用加密安全的随机数生成器
  • 足够空间:36^8 ≈ 2.8 万亿种组合,防止暴力攻击

设计启示

1. 分层的价值

Claude Code 的五层架构展示了分层的价值:

  • 职责分离:每层专注于自己的职责,降低耦合
  • 易于维护:修改某一层不会影响其他层
  • 灵活扩展:新增功能只需修改相关层次
  • 团队协作:不同团队可以负责不同层次

2. 上下文传递的优雅设计

ToolUseContext 的设计展示了如何在分层架构中优雅地传递上下文:

  • 类型安全:TypeScript 确保类型安全
  • 按需访问:每层可以访问需要的上下文
  • 最小耦合:通过接口而非实现进行通信
  • 易于测试:可以模拟上下文进行测试

3. 基础设施的重要性

cost-tracker.ts 和 Task.ts 展示了基础设施层的重要性:

  • 精确追踪:费用追踪精确到每个 token
  • 状态管理:任务状态管理清晰明确
  • 持久化支持:支持状态的持久化和恢复
  • 分析支持:为分析提供数据支持

4. 类型安全的保证

TypeScript 的类型系统在分层架构中发挥了关键作用:

  • 接口定义:清晰的接口定义
  • 类型推导:自动类型推导减少错误
  • 编译时检查:编译时发现错误
  • 文档作用:类型即文档

思考题

  1. 分层的权衡:五层架构是否过多?在某些场景下是否可以合并某些层次?如何判断分层的粒度?

  2. 上下文传递的性能ToolUseContext 在每一层之间传递,是否会带来性能问题?如何优化上下文传递的性能?

  3. 基础设施层的扩展:如果要在基础设施层添加新的功能(例如性能监控),应该如何设计?需要修改哪些文件?

  4. 跨层通信的模式:在五层架构中,跨层通信应该遵循什么原则?是否允许跳层通信?如何避免循环依赖?

  5. 类型系统的局限:TypeScript 的类型系统在分层架构中发挥了重要作用,但它有什么局限?如何弥补这些局限?

小结

Claude Code 的五层架构设计展示了软件工程中分层架构的威力。从 CLI 层到基础设施层,每一层都有明确的职责和清晰的接口。这种设计让复杂的系统变得易于理解和维护。

关键设计要点:

  • 职责分离:每层专注于自己的职责
  • 上下文传递ToolUseContext 优雅地传递上下文
  • 基础设施支撑:费用追踪、任务管理等基础设施提供支撑
  • 类型安全:TypeScript 确保类型安全

分层架构不仅仅是一种设计模式,更是一种思维方式。它让我们能够以结构化的方式思考和构建复杂的软件系统。在"对话即编程"范式中,分层架构为这一范式提供了坚实的技术基础。