关于 code-architecture
code-architecture 是帮助开发者组织单体应用内部代码的架构技能,适用于新项目选型和现有代码诊断。它介绍分层、六边形、功能导向等模式,并讨论依赖反转、模块边界与测试布局。
主要功能
- 01清洁与六边形架构模式
- 02功能导向的代码组织
- 03依赖反转与注入
- 04模块边界和公共 API
- 05循环依赖诊断
- 06按架构层规划测试
适用场景
- 01为新项目选择代码架构
- 02整理不断增长的应用模块
- 03排查控制器逻辑过重或循环依赖
- 04调整测试以隔离业务逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill code-architecture安装后在 Claude Code 中输入/code-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.claude-plugin/plugin.jsonreferences/architecture-patterns.mddependency-inversion.md详细介绍
代码架构
能够经受现实考验的架构模式和代码组织方式。目标是让代码在编写 18 个月后依然易于理解、测试和修改。本文涵盖模块和应用层面的决策——不涉及如何拆分服务,不涉及如何设计架构,而是介绍如何在单个可部署单元内组织代码。
适用场景
适用于:
- 为新项目选择架构模式(整洁架构、六边形架构、按功能组织、纵向切片)
- 诊断现有代码中的架构问题(臃肿的控制器、循环依赖、纠缠不清的业务逻辑)
- 应用依赖倒置,解开紧密耦合的代码
- 决定文件夹结构和模块组织方式
- 在模块层面应用 SOLID 原则(而不仅仅是类层面)
- 设置依赖注入容器
- 定义模块边界和 barrel 导出
- 根据架构层设计测试金字塔
不适用于:
- 将单体拆分为微服务(请使用
microservices-patterns) - 数据库架构设计、规范化、查询优化(请使用
database-design-patterns) - 特定框架的路由或中间件设置
- CI/CD 流水线架构
核心决策:选择哪种架构模式?
flowchart TD
Start[New project or restructuring?] --> Team{Team size?}
Team -->|Solo / 2 dev| Size{App complexity?}
Team -->|3-8 dev| MedComplex{Domain complexity?}
Team -->|8+ dev| Large[Feature-based / Vertical Slice]
Size -->|Simple CRUD, low domain logic| Simple[Flat + MVC or Feature-Based]
Size -->|Complex domain, long-lived| Hex[Hexagonal Architecture]
MedComplex -->|Low: thin business layer| FeatureBased[Feature-Based Organization]
MedComplex -->|High: rich business rules| Clean[Clean Architecture]
MedComplex -->|Mixed: CRUD + some complex| Vertical[Vertical Slice Architecture]
Simple --> SCheck{Will it grow?}
SCheck -->|No, short-lived| MVC[Layer-based MVC is fine]
SCheck -->|Yes| FeatureBased
Large --> Testing{Testing rigor?}
Testing -->|Low: mostly E2E| Vertical
Testing -->|High: unit + integration| Clean
Clean --> ClNote[Entities → Use Cases → Interface Adapters → Frameworks]
Hex --> HexNote[Domain core ↔ Ports ↔ Adapters]
FeatureBased --> FBNote[features/X contains all layers for X]
Vertical --> VSNote[Each slice owns its full stack top to bottom]
整洁架构分层
经典的洋葱结构——每一环只依赖内侧,绝不依赖外侧。
graph TB
subgraph Frameworks ["Frameworks & Drivers (outer)"]
DB[(Database)]
HTTP[HTTP Server]
UI[UI / CLI]
External[External APIs]
end
subgraph Adapters ["Interface Adapters"]
Controllers[Controllers / Routes]
Presenters[Presenters]
Gateways[Repository Implementations]
DTOs[DTOs / View Models]
end
subgraph UseCases ["Use Cases (Application Layer)"]
UC1[CreateOrder]
UC2[ProcessPayment]
UC3[NotifyUser]
Ports[Repository Interfaces]
end
subgraph Entities ["Entities (Domain)"]
Order[Order]
User[User]
Payment[Payment]
Rules[Business Rules]
end
HTTP --> Controllers
Controllers --> UC1
UC1 --> Ports
Gateways --> Ports
DB --> Gateways
UC1 --> Order
依赖规则:源代码依赖只能指向内侧。实体不了解用例。用例不了解控制器。控制器不了解数据库驱动程序。
各层包含的内容:
| 层 | 内容 | 示例 |
|---|---|---|
| 实体 | 企业业务规则 | Order、Payment、领域事件、值对象 |
| 用例 | 特定于应用的业务逻辑 | CreateOrderUseCase、RefundPaymentUseCase |
| 接口适配器 | 在用例与外部形式之间转换数据 | 控制器、Presenter、Repository 实现 |
| 框架 | 外部工具、数据库、UI 框架 | Express、Postgres、React |
六边形架构(端口与适配器)
对许多团队来说,它比整洁架构更容易理解:所有内容都通过端口(接口)连接,适配器负责实现这些端口。
graph LR
subgraph Domain ["Domain (Hexagon)"]
Logic[Business Logic]
DPorts[Driven Ports<br/>interfaces the domain calls]
end
subgraph Driving ["Driving Adapters<br/>(what drives the app)"]
HTTP2[HTTP Controller]
CLI2[CLI Command]
Test[Test Harness]
end
subgraph Driven ["Driven Adapters<br/>(what the app drives)"]
DB2[(PostgreSQL Adapter)]
Email[SendGrid Adapter]
Cache[Redis Adapter]
end
HTTP2 -->|calls| Logic
CLI2 -->|calls| Logic
Test -->|calls| Logic
Logic -->|calls port| DPorts
DPorts -.->|implemented by| DB2
DPorts -.->|implemented by| Email
DPorts -.->|implemented by| Cache
六边形架构优于整洁架构的场景:需要轻松替换基础设施时(测试使用内存实现,生产环境使用 Postgres),或业务逻辑足够丰富、值得隔离,但不需要整洁架构完整分层时。
按功能组织与按层组织
按层组织(传统 MVC)
src/
controllers/
user.controller.ts
order.controller.ts
payment.controller.ts
services/
user.service.ts
order.service.ts
payment.service.ts
repositories/
user.repository.ts
order.repository.ts
models/
user.model.ts
order.model.ts
问题:添加或修改“orders”功能,需要改动四个目录中的文件。理解 orders 领域,需要在不同文件夹之间来回切换上下文。
按功能组织(适用于不断扩展的应用)
src/
features/
orders/
orders.controller.ts
orders.service.ts
orders.repository.ts
orders.schema.ts
orders.types.ts
orders.test.ts
index.ts ← public API (barrel export)
users/
users.controller.ts
users.service.ts
...
index.ts
payments/
...
shared/
database/
logger/
config/
优点:所有与订单相关的代码都集中在一起。删除某个功能只需删除一个文件夹。让开发者熟悉 orders 领域,只需查看一个目录。
规则:功能只能从 shared/ 和彼此的公开 index.ts 导入内容。绝不能访问彼此的内部实现。
反模式:将业务逻辑放在控制器中(臃肿的控制器)
新手:“请求是通过控制器进来的,所以我也把逻辑放在那里。这样很方便。”
// Fat controller — don't do this
app.post('/orders', async (req, res) => {
const { userId, items } = req.body;
// Business logic #1: calculate total
const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
// Business logic #2: apply discount
const user = await db.users.findById(userId);
const discount = user.memberSince < oneYearAgo ? 0.1 : 0;
const finalTotal = total * (1 - discount);
// Business logic #3: check inventory
for (const item of items) {
const stock = await db.inventory.findById(item.productId);
if (stock.quantity < item.qty) {
return res.status(409).json({ error: 'Out of stock' });
}
}
// Side effects mixed in
const order = await db.orders.create({ userId, items, total: finalTotal });
await emailService.send(user.email, 'order-confirmation', { order });
await inventory.decrement(items);
res.json(order);
});
专家:控制器是 HTTP 适配器。它们将 HTTP 转换为领域对象,调用用例或服务,然后将领域对象转换为 HTTP 响应。所有业务逻辑都应属于用例或领域服务,这样无需 HTTP 上下文也能进行测试。
// Lean controller
app.post('/orders', async (req, res) => {
try {
const command = CreateOrderCommand.fromRequest(req.body); // Validate + map
const order = await createOrderUseCase.execute(command); // All logic here
res.status(201).json(OrderPresenter.toJSON(order)); // Map to response
} catch (error) {
errorHandler(res, error);
}
});
// Use case: testable, framework-agnostic
class CreateOrderUseCase {
constructor(
private readonly orderRepo: OrderRepository,
private readonly inventoryService: InventoryService,
private readonly discountPolicy: DiscountPolicy,
private readonly notifications: NotificationPort,
) {}
async execute(command: CreateOrderCommand): Promise<Order> {
const user = await this.orderRepo.findUser(command.userId);
const discount = this.discountPolicy.calculate(user);
await this.inventoryService.reserveItems(command.items);
const order = Order.create(command.items, discount);
await this.orderRepo.save(order);
await this.notifications.orderCreated(order, user);
return order;
}
}
识别方式:逻辑超过约 20 行的控制器、直接导入数据库模型的控制器、包含嵌套 if-else 业务条件的控制器。
反模式:架构太空人(为了抽象而抽象)
新手:“我要添加一个 Repository 接口、一个 Repository 实现、一个 Service、一个 ServiceInterface、一个用于创建 Service 的 Factory、一个 EventBus,以及一个 CQRS 命令处理器。这就是企业级架构。”
专家:每种抽象都有成本:更多文件、更多间接层、更难上手、更多维护工作。抽象是一种投资,只有在能够支持测试、替换或代码复用时才能带来回报。如果你添加了一个只有单一实现且永远不会改变的 UserServiceInterface,就等于付出了抽象成本,却没有获得收益。
不妨问问:“这种抽象能让我做到什么原本做不到的事?”
- Repository 接口 → 在测试中用内存数据库替换真实数据库。立刻见效。
- Service 接口 → 如果始终只有一个 Service,这只是在走形式。
- Factory 模式 → 对象创建复杂或存在多种策略时才有回报。
- Event bus → 许多组件需要响应领域事件,但彼此无需了解对方时才有回报。
检测:名为 *Interface.ts、*Abstract.ts、*Factory.ts 的文件只有一个实现者和一个调用方,并且该实现者从未改变。
时间线:企业 Java(2005-2015)将“凡事都抽象”变成了默认做法。Spring Framework 助长了这种做法。2015 年之后,Node.js 和 Go 社区以“无聊技术”原则作出反击。到了 2026 年,合适的抽象程度取决于具体情况——既不是零,也不是最大化。
反模式:循环依赖
新手:“Order 模块需要了解 Users,而 User 模块需要检查用户的订单。所以我要让它们互相导入。”
// orders/order.service.ts
import { UserService } from '../users/user.service'; // Order → User
// users/user.service.ts
import { OrderService } from '../orders/order.service'; // User → Order
// Node.js will silently give you `undefined` at runtime
// Jest will give you cryptic "Cannot access before initialization" errors
专家:循环依赖表明领域建模存在问题。两个确实需要彼此的模块,要么合并成一个模块,要么共用一个双方都依赖的第三模块,要么通过事件或接口通信。
解决策略:
- 合并:如果 Order 和 User 确实密不可分,就把它们放进
accounts/ - 提取共享模块:创建双方都可以导入的
order-summary/ - 通过接口反转依赖:User 模块定义
OrderSummaryPort接口;Orders 实现该接口;User 永远不导入 Orders - 事件驱动:User 响应
OrderCreated事件,而不是直接调用 OrderService
# Detect circular deps
npx madge --circular src/
npx dpdm --circular src/index.ts
# ESLint rule (add to .eslintrc)
# "import/no-cycle": "error"
检测:启动时模块值为 undefined 的运行时错误、违反 ESLint import/no-cycle 规则的情况,以及 madge 输出的循环依赖。
实践中的依赖倒置
SOLID 中的 D:依赖抽象,而非具体实现。在模块层面上的应用:
// Bad: Use case is coupled to Postgres
class CreateOrderUseCase {
constructor(private readonly db: PostgresConnection) {}
async execute(cmd: CreateOrderCommand) {
await this.db.query('INSERT INTO orders ...');
}
}
// Good: Use case depends on an interface
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: OrderId): Promise<Order | null>;
}
class CreateOrderUseCase {
constructor(private readonly orderRepo: OrderRepository) {}
async execute(cmd: CreateOrderCommand) {
const order = Order.create(cmd);
await this.orderRepo.save(order); // No SQL, no Postgres, no coupling
}
}
// Production: Postgres implements the interface
class PostgresOrderRepository implements OrderRepository {
async save(order: Order): Promise<void> { /* Postgres SQL */ }
async findById(id: OrderId): Promise<Order | null> { /* Postgres SQL */ }
}
// Tests: in-memory implements the same interface
class InMemoryOrderRepository implements OrderRepository {
private orders = new Map<string, Order>();
async save(order: Order) { this.orders.set(order.id, order); }
async findById(id: OrderId) { return this.orders.get(id) ?? null; }
}
DI 容器适用的情况:依赖项很多,需要进行装配,且手动装配容易出错或重复时。NestJS、InversifyJS(TypeScript)、Python 的 dependency-injector、Spring(Java)。
DI 容器用力过猛的情况:简单脚本、依赖项较少的小型服务、无服务器函数、Go 项目(构造函数注入符合惯例且已足够)。
模块边界与 Barrel 导出
每个功能/模块都应通过 index.ts 暴露公共 API:
// features/orders/index.ts — public API
export { CreateOrderUseCase } from './create-order.use-case';
export { OrderRepository } from './order.repository.interface';
export type { Order, OrderStatus } from './order.entity';
// NOT exported: internal helpers, SQL queries, implementation details
// Other modules import from the public API only
import { CreateOrderUseCase } from '@/features/orders';
// NOT: import from '@/features/orders/create-order.use-case'
使用 ESLint 强制执行:
// .eslintrc
{
"rules": {
"import/no-internal-modules": ["error", {
"allow": ["**/*.test.ts", "**/index.ts"]
}]
}
}
测试架构(测试金字塔中的位置)
每个架构层都有适合的测试类型:
| 层 | 测试类型 | 速度 | 覆盖率 |
|---|---|---|---|
| 实体 / 领域 | 单元测试 | 即时 | 90%+ |
| 用例 / 应用 | 使用模拟对象的单元测试 | 快 | 80%+ |
| 接口适配器 | 集成测试 | 中等 | 70%+ |
| 框架 / 外部 | E2E / 契约测试 | 慢 | 关键路径 |
规则:业务逻辑测试不应要求启动服务器、连接数据库或发起网络调用。如果你的用例测试需要真实数据库,说明基础设施关注点已经渗入架构中的领域。
参考资料
references/architecture-patterns.md— 查看详细模式:整洁架构分层、六边形架构的端口与适配器、垂直切片、基于功能的组织方式及示例references/dependency-inversion.md— 查看 TypeScript 和 Python 中的 IoC 容器、构造函数注入模式、DI 何时属于过度设计,以及常见的 DI 反模式