从意图到架构

3 min read
Zekari
软件设计架构技术规划

规划的本质

大部分技术文档是倒着写的。它们展示最终方案,隐藏思考过程。这让规划看起来像是突然出现的,像是某种天赋。

但真实的过程不是这样的。好的方案不是想出来的,而是从问题域中自然流出的。

规划是一个场。信息进入,意图凝结,策略环绕,路径收敛。整个过程类似行星形成:从星云到恒星,从可能到必然。

差距即设计空间

有了 Want(我想要什么)和 Have(我有什么),差距就显现了。这个差距不是障碍,而是设计的全部空间。

想要的:一个评论系统。用户可以在文章任意位置留下想法,可以在文末进行讨论。

拥有的:一个静态生成的博客。内容在构建时确定,页面在部署时就已经存在。

差距:静态内容与动态交互之间的鸿沟。

这个差距揭示了真正的问题:如何在构建时和运行时之间建立桥梁。

💡 Click the maximize icon to view in fullscreen

差距的形状决定了方案的形状。

意图井:问题的凝结

所有信息都进入这个场。功能需求、技术约束、用户期待、性能指标。它们在这里碰撞、混合、沉淀。

噪音在此消散。"评论需要富文本编辑器吗?""是否需要实时更新?""管理员审核功能的优先级?"这些问题暂时悬置。

真正的问题在此凝结:

核心挑战一:身份系统。评论需要知道"谁在说话"。 核心挑战二:数据持久化。评论需要被存储和检索。 核心挑战三:文本定位。行间评论需要准确标记在原文中的位置。

这三个挑战不是并列的。它们有轻重。

身份系统可以外包(Clerk)。数据持久化有成熟方案。但文本定位是独特的,是这个系统特有的复杂度。

意图核心形成:构建一个在静态页面上运行的、能准确定位文本片段的评论系统

当你能用一句话说清楚"真正要解决的问题",意图就凝结了。

这句话不是功能列表,不是技术栈,而是问题的本质。

策略云:可能性的展开

围绕意图核心,可能性开始旋转。每条路径都是一种势能分布。

路径 A:全客户端

认证在客户端。评论存储在第三方服务。所有交互在浏览器完成。

这条路径的势能:低。实现简单,快速上线。 这条路径的代价:SEO 弱,首屏慢。 这条路径的边界:评论永远不会成为内容的一部分。

路径 B:混合渲染

认证在客户端。评论存储在自己的数据库。服务端 API 处理 CRUD。客户端组件处理交互。

这条路径的势能:中等。需要建立 API 层,需要数据库。 这条路径的代价:开发周期增加,基础设施复杂度上升。 这条路径的边界:评论可以被预加载,可以被索引,可以逐步优化。

路径 C:完全集成

Prisma ORM,tRPC 类型安全,WebSocket 实时更新,Redis 缓存。

这条路径的势能:高。企业级架构,可扩展性强。 这条路径的代价:开发周期长,维护成本高,过度工程风险。 这条路径的边界:适合大规模应用,对小博客来说是负担。

💡 Click the maximize icon to view in fullscreen

三条路径共存。此刻不急于坍缩,让势能场充分展开。

选择漏斗:势能的倾斜

场开始倾斜。不是人为选择,而是约束条件形成了势能差。

时间约束:7-12 天开发周期。路径 C 不现实。 资源约束:个人博客,没有运维团队。路径 C 的维护成本过高。 扩展性需求:未来可能增加功能。路径 A 的天花板太低。

势能场自然倾向路径 B。

混合渲染是平衡点。它不是最简单的,也不是最强大的,但它在当前约束下提供了最好的势能分布。

最优解不是选出来的,是场自然流向的。

当一个方案自然地解决了大部分问题,同时避开了大部分陷阱,它就是当下的最优解。

不需要完美,只需要合适。

理解边界

方案确定,设计开始。第一步是理解边界。

构建时边界

  • MDX 文章内容在此确定
  • 静态 HTML 在此生成
  • 路由结构在此固定

运行时边界

  • 用户登录状态在此确认
  • 评论数据在此加载
  • 交互逻辑在此执行

评论系统必须跨越这条边界。但跨越的方式有讲究。

不能在构建时处理评论数据(数据是动态的)。不能在运行时重新生成整个页面(会破坏静态生成的优势)。

正确的方式是:静态内容先渲染,动态评论后注入。两个世界通过 hydration 连接。

定义数据

边界清晰后,数据结构自然显现。

// 评论的本质
interface Comment {
  id: string
  userId: string      // 来自 Clerk
  postSlug: string    // 关联到文章
  content: string     // 评论内容
  createdAt: Date
  parentId?: string   // 支持回复
}

// 行间评论的特殊性
interface InlineComment extends Comment {
  anchor: {
    selectedText: string  // 用户选中的文本
    prefix: string        // 前后上下文
    suffix: string
    offsetY: number       // 视觉位置
  }
}

数据结构不是凭空设计的,而是从问题域中提取的。

评论需要知道"谁说的"(userId),"在哪说的"(postSlug),"说了什么"(content)。行间评论额外需要知道"在哪个位置说的"(anchor)。

每个字段都对应一个真实需求。没有多余,没有遗漏。

行间评论最难的部分是 anchor

文章内容可能更新,DOM 结构可能变化,用户的浏览器可能不同。如何在这些变化中依然准确定位?

答案是多重定位策略:

  • 精确匹配(selectedText + prefix + suffix)
  • 模糊匹配(允许轻微变化)
  • CSS 路径回退
  • 视觉位置估算

不依赖单一方法,而是建立定位的鲁棒性。

设计函数

数据确定后,操作数据的方式也确定了。

// 评论的生命周期
async function createComment(data: CreateCommentInput): Promise<Comment>
async function getComments(postSlug: string): Promise<Comment[]>
async function updateComment(id: string, content: string): Promise<Comment>
async function deleteComment(id: string): Promise<void>

// 行间评论的特殊操作
function createTextAnchor(selection: Selection): Anchor
function locateTextAnchor(anchor: Anchor): Position
function highlightText(range: Range, commentId: string): void

函数的接口不是随意设计的。它们直接对应数据的状态转换。

创建评论需要什么信息?CreateCommentInput。获取评论需要什么条件?文章的 postSlug。定位文本需要什么输入?用户的 Selection

每个函数都是一个纯粹的意图:做一件事,把它做好。

建立抽象

最后一步是隐藏复杂度。

┌─────────────────────────────────────┐
│ UI 层                               │
│ CommentSection, CommentForm         │
│ 只关心"显示"和"交互"                │
└─────────────────────────────────────┘
         ↓ 调用
┌─────────────────────────────────────┐
│ Hook 层                             │
│ useComments(), useTextSelection()   │
│ 管理状态和副作用                    │
└─────────────────────────────────────┘
         ↓ 调用
┌─────────────────────────────────────┐
│ Service 层                          │
│ CommentService, TextAnchorService   │
│ 处理业务逻辑                        │
└─────────────────────────────────────┘
         ↓ 调用
┌─────────────────────────────────────┐
│ API 层                              │
│ fetch('/api/comments')              │
│ 与后端通信                          │
└─────────────────────────────────────┘

每一层都有清晰的职责。UI 层不知道数据如何存储,Service 层不知道数据如何渲染。

抽象的目的不是炫技,而是让每一层都能独立演化。未来换数据库?只改 API 层。未来改 UI 框架?只改 UI 层。

好的抽象是看不见的。它不增加理解成本,只减少耦合。

涌现的本质

回顾整个过程:

  1. 差距显现设计空间
  2. 意图凝结核心问题
  3. 策略展开可能路径
  4. 场域倾斜自然选择
  5. 边界约束设计形状
  6. 数据提取问题本质
  7. 函数定义状态转换
  8. 抽象隐藏实现细节

没有一步是凭空出现的。每一步都从前一步流出。

这不是线性过程,而是势能的流动。信息进入场域,意图成为引力源,策略围绕旋转,最优解自然涌现。

技术规划的本质不是计划,而是理解问题域的势能分布,然后让方案沿着最自然的路径流动。

试着不要马上跳到方案。

先让问题充分展开。 让所有可能性旋转一会儿。 看看势能场自然倾向哪里。

答案会自己出现。

Related Posts

Articles you might also find interesting

编码前的思考

1 min read

软件设计不是从代码开始的。在动手之前,有一套思维框架值得遵循:理解边界、定义数据、设计函数、建立抽象。

软件设计思维框架
Read More

调用链路追踪法:从断点到根因

2 min read

功能失效的背后,是一条完整的调用链路。追踪这条链路,定位断点,才能从根本上解决问题。

架构调试
Read More

依赖注入

2 min read

依赖注入不是关于框架或工具,而是关于控制权的转移。理解这个转移,就理解了软件设计的核心原则。

软件设计系统思维
Read More

在运行的系统上生长新功能

3 min read

扩展不是推倒重来,而是理解边界,找到生长点。管理层作为观察者和调节器,附着在核心系统上,监测它,影响它,但不改变它的运行逻辑。

系统设计架构
Read More

引入懒加载模式

1 min read

懒加载不是优化技巧,而是关于时机的选择。何时创建,决定了系统的效率和复杂度。

软件设计性能优化
Read More

队列、可靠性与系统边界

1 min read

探讨消息队列系统如何通过时间换空间,用异步换解耦,以及可靠性背后的权衡

架构可靠性
Read More

Context 驱动的认证状态管理

3 min read

认证系统的核心不在登录按钮,而在状态同步。如何让整个应用感知用户状态变化,决定了用户体验的流畅度。

软件设计认证系统
Read More