编程 架构即代码不是画图:LikeC4 深度解剖——从 DSL 内核到多视图布局引擎的工程真相

2026-07-25 02:43:56 +0800 CST views 6

架构即代码不是画图:LikeC4 深度解剖——从 DSL 内核到多视图布局引擎的工程真相

做过几年后端的人,大概都经历过同一种荒诞:入职第一周被丢过来一张 架构图_v3_final_最终版(1).png,图上有六个方框、十几条线、三种颜色,没人说得清哪根线还活着;三个月后你改了一个服务的通信方式,那张图仍然静静躺在 Confluence 的角落,像一具没人认领的尸体。

架构文档过时,不是纪律问题,是机制问题。只要图和代码是两套东西,图就一定会腐烂。LikeC4 试图从根上解决这件事:把架构写成代码,用编译器保证它永远自洽,用布局引擎自动生成图,用 Git 管理它的演进。它不是又一个画图工具,它是一门语言加一套编译工具链。

这篇文章不打算教你"十分钟画出第一张图"——那种教程网上一抓一大把。我想带你钻进去看:LikeC4 的 DSL 到底是怎么设计的、它凭什么号称"单一事实源"、背后的解析器和布局引擎是什么架构、它跟 Structurizr / Mermaid / PlantUML 的本质差异在哪、以及在真实团队里怎么把它落地到 CI 而不变成新的负担。

一、先把问题定义清楚:为什么"架构图"天生会烂

在聊 LikeC4 之前,得先想明白一件事:为什么架构图这么难维护,而代码却能维护?

答案是反馈回路。代码有编译器、有测试、有 CI、有线上报错,任何一个环节出错都会立刻打你脸,所以它被迫保持正确。而架构图呢?画错了没人报错,过时了没人报警,它和真实系统之间没有任何强制的一致性检查。这是一个没有反馈的系统,熵只会单调增加。

传统画图工具(draw.io、Visio、甚至白板拍照)本质上都在生产一次性快照。它们记录的是"某一刻某个人脑子里的系统长什么样",而不是"系统现在是什么样"。快照天生和现实脱节。

C4 模型(Context / Container / Component / Code 四个抽象层次)在方法论上前进了一步,它规定了"该画什么、画到哪一层",但 C4 本身只是画法约定,你依然可以用 draw.io 去画 C4,结果还是会烂。

真正的转折点是 Architecture as Code(架构即代码):把架构描述变成纯文本,纳入版本控制,用工具生成图。这样架构描述就获得了和代码一样的反馈回路——可 diff、可 review、可校验、可 CI。Structurizr DSL 是这条路的先行者,而 LikeC4 是把这条路走得更彻底、更贴近现代前端工程体验的后来者。

一句话总结 LikeC4 的立场:图是产物,不是源。源是那份 .c4 文本。

二、DSL 三段式:specification / model / views

LikeC4 的语言分成三个泾渭分明的部分。理解了这三段,就理解了它的世界观。

2.1 specification:先定义"世界里有哪些概念"

大多数画图工具直接让你拖框,而 LikeC4 逼你先声明你的建模词汇表。这一步看似啰嗦,实则是它灵活性的根源——它不把 C4 的 Person/System/Container/Component 写死,而是让你自己定义元素类型。

specification {
  element actor {
    style {
      shape person
      color secondary
    }
  }
  element system
  element container
  element component {
    style {
      shape rectangle
    }
  }

  // 关系也可以有类型
  relationship async {
    line dashed
    head open
  }
  relationship sync

  // 标签(tag)用来做筛选和着色
  tag deprecated {
    color muted
  }
  tag critical {
    color red
  }
}

这里的关键设计是:元素类型是开放的。你可以只用 C4 的四层,也可以定义 queuetopiclambdas3bucket,甚至业务概念 bounded-context。工具不预设你的领域,你的领域自己说了算。这跟 Structurizr 相对固定的元素体系是明显区别。

2.2 model:描述"世界里真实存在什么以及它们怎么互动"

model 段是系统的骨架。它是一棵可以任意嵌套的树,节点是元素,边是关系。

model {
  customer = actor 'Customer' {
    description '使用我们产品的终端用户'
  }

  saas = system 'Our SaaS Platform' {
    description '核心业务系统'

    ui = container 'Web Frontend' {
      technology 'React + Vite'
      description '浏览器端单页应用'
    }

    api = container 'API Gateway' {
      technology 'Go / Gin'

      authz = component 'Auth Service' {
        technology 'JWT + OAuth2'
      }
      orders = component 'Order Service'
    }

    db = container 'Primary DB' {
      technology 'PostgreSQL 18'
      style {
        shape cylinder
      }
    }

    // 内部关系
    ui -> api 'REST over HTTPS'
    api.orders -> db 'reads/writes' {
      #critical
    }
    api.authz -> db 'validates sessions'
  }

  // 跨边界关系
  customer -> ui 'opens in browser'
}

注意几个工程上很讲究的点:

  1. 嵌套即边界authz 写在 api 里,就表示它是 API Gateway 的一个组件。层级关系不用额外声明 partOf,缩进和作用域直接表达了归属。
  2. 点路径引用api.orders 这种写法让你在任意深度精确引用元素,作用域清晰。
  3. 关系是一等公民ui -> api 'REST over HTTPS' 里,箭头、标签、类型、tag 全都能挂上去。关系不是画上去的线,是模型里的数据。
  4. 单一事实源。你只在 model 里声明一次 api.orders -> db,之后所有视图里出现的这条线,都是同一条数据的不同投影。改一处,全局同步。

第 4 点是整个工具的灵魂。传统画图里,同一条依赖你在系统全景图画一遍、在容器细节图又画一遍,两处会各自漂移。LikeC4 里它只存在一次。

2.3 views:从同一个模型投影出无数张图

model 定义了完整的真相,但你从来不会想一次看到全部真相——那会是一张蜘蛛网。views 段就是"镜头",它决定每张图看什么、看多深、怎么摆。

views {
  // 系统全景:只看顶层
  view landscape {
    title 'System Landscape'
    include *
  }

  // 聚焦 SaaS 内部
  view saasDetail of saas {
    title 'SaaS Container View'
    include *
    include customer

    style customer {
      color muted
    }
  }

  // 只看和数据库相关的东西
  view dbDependencies {
    title 'Who Touches the DB'
    include db
    include db <-> *   // db 的所有出入关系及对端
  }

  // 动态视图:描述一次请求的时序
  dynamic view checkoutFlow {
    title 'Checkout Sequence'
    customer -> ui 'clicks pay'
    ui -> api 'POST /checkout'
    api -> api.orders 'create order'
    api.orders -> db 'INSERT order'
  }
}

这里体现了 LikeC4 相对同类工具的一个强项:predicate(谓词)驱动的视图include db <-> * 不是手工挑元素,而是声明式地说"把 db 以及所有和它有关系的对端拉进来"。当你新增一个服务连到 db,这张图会自动多出那个服务,你什么都不用改。这就是"图永远最新"的技术兑现方式。

dynamic view 则解决了 C4 静态图表达不了"时序/流程"的痛点,它本质上是从静态模型里挑出一串已存在的关系,按你给的顺序排成时序图。注意——它只能引用 model 里真实存在的关系,你没法在动态视图里凭空画一条模型里不存在的线。这个约束是故意的:它保证动态图也不会撒谎。

三、内核架构:一份 .c4 文本是怎么变成交互式图的

会用是一回事,理解它凭什么可靠是另一回事。LikeC4 的技术栈拆开看,是一条相当典型但打磨精良的"语言工程"流水线。

3.1 解析层:Langium 撑起的语言服务

LikeC4 的 DSL 不是用正则硬凑的,它建立在 Langium(TypeScript 生态的语言工程框架,Xtext 的精神继承者)之上。这带来几个直接的工程收益:

  • 完整的 LSP(Language Server Protocol)支持。这就是为什么它的 VS Code 插件能做自动补全、跳转定义、悬停提示、实时错误标红——这些不是插件硬编码的,是语言服务器天然产出的能力。
  • 真正的 AST。文本被解析成抽象语法树,元素引用被解析成符号(symbol)而非字符串匹配。当你写 api.orders -> db,工具知道 db 是哪个具体节点,如果 db 不存在就直接报错。
  • 作用域与交叉引用校验。嵌套作用域、命名冲突、悬空引用,都在解析阶段被抓出来。

可以把它类比成:LikeC4 给架构描述配了一个 TypeScript 编译器级别的前端。你写错一个元素名,它当场红给你看,而不是等你导出图之后才发现少了个框。

3.2 语义层:从 AST 到 Computed Model

解析出 AST 只是第一步。真正有价值的是语义模型(computed model)——把 specification 里的类型、model 里的实例、tag、style 全部解析、合并、继承之后,得到一份规范化的、可查询的架构数据结构。

视图里的谓词(include *include db <-> *)就是在这份 computed model 上求值的。你可以把它理解成一个小型查询引擎:视图是查询,computed model 是数据库,求值结果是"这张图该出现哪些节点和哪些边"。

这一层的存在,是"一处声明、多视图投影"能成立的技术基础。model 里的每条关系是一行数据,每个视图是一条 SELECT

3.3 布局层:自动布局才是画图工具的胜负手

这是最容易被低估、却最决定体验的部分。你声明了节点和边,谁来决定它们摆在哪、线怎么走不打结?

传统 draw.io 是人肉摆。Mermaid / PlantUML / Structurizr 常借助 Graphviz 的 DOT 布局。LikeC4 走的是自动布局路线,把"哪些节点、哪些边"交给布局算法去算出坐标,开发者不用操心像素级摆放。

自动布局的价值在于:当模型变化(多一个服务、多一条依赖),图会自动重排,而不需要人回去挪框。代价是你对精确摆位的控制变弱——但对"永远最新"这个目标来说,这个取舍是对的。你要的不是像素级手工艺品,而是一张随时正确、随时能重新生成的图。

对于确实需要人工微调的场景,LikeC4 也支持在视图里用 style、手动排序、以及对特定视图保存布局的能力,属于"默认自动、按需干预"的合理设计。

3.4 渲染与分发层:一处模型,处处可嵌

算出坐标之后,怎么把图交付出去?LikeC4 的分发能力是它相对老牌工具最"现代前端"的地方:

  • likec4 start:起一个本地开发服务器,热重载,边写边看。
  • likec4 build:产出一个静态站点,可以直接部署到任何静态托管。
  • likec4 export:导出 PNG / SVG / mermaid 等格式,塞进 Markdown 或 PPT。
  • likec4 codegen react / Web Components:把图生成为 React 组件或 Web Component,嵌进你自己的文档站、内部门户、Storybook。
  • Vite 插件:在前端工程里直接 import 架构视图。

也就是说,同一份 .c4,既能当交互式网站,又能当静态图片,又能当 React 组件。渲染目标是可插拔的,源只有一个。

3.5 AI 层:MCP 让 Agent 直接读你的架构

LikeC4 明确把自己定位成"LLM-friendly",并提供 MCP(Model Context Protocol)server / API,把架构上下文暴露给 AI Agent。

这件事的意义比听起来大。传统架构图是像素,AI 只能"看图"猜结构;而 LikeC4 的架构是结构化文本 + 语义模型,Agent 可以查询它:"订单服务依赖了哪些下游?""哪些组件标了 deprecated?""这次改动影响到哪些视图?"——这是把架构从"给人看的图"升级成"给人和机器都能查询的知识库"。在 AI 辅助开发越来越重的当下,这个设计方向是有前瞻性的。

四、横向对比:它到底和 Structurizr / Mermaid / PlantUML 差在哪

选型时最实际的问题就是这个。我按几个真正影响长期维护成本的维度拉一张对比(用文字,不用表格,方便在各平台阅读)。

建模 vs 画图

  • Mermaid / PlantUML:本质是"文本画图"。你写的是"画一个框、画一条线",它们没有"模型"概念,同一个组件在两张图里是两个无关的文本片段。适合小图、单图、README 里顺手一画。
  • Structurizr / LikeC4:本质是"文本建模"。你写的是"存在一个组件、存在一条关系",图是从模型投影出来的。适合中大型系统、需要多视图、需要长期维护。

元素体系的灵活度

  • Structurizr:紧贴 C4,元素类型相对固定(person/software system/container/component)。规范,但想表达 C4 之外的概念(队列、Lambda、限界上下文)会别扭。
  • LikeC4:specification 里自定义元素类型和关系类型,任意嵌套层级。更自由,也更需要团队自律去约定规范。

视图机制

  • Mermaid / PlantUML:一段代码一张图,图之间无关联。
  • Structurizr:视图是模型的投影,但 DSL 表达力和自定义程度有其边界。
  • LikeC4:谓词驱动的视图(include *a <-> *、按 tag 筛选),动态视图,自动跟随模型变化。视图表达力是它的强项之一。

布局与渲染

  • PlantUML / Structurizr(配合 Graphviz):DOT 布局,成熟稳定。
  • LikeC4:自动布局 + 现代前端渲染,交互式导航、drill-down、可生成 React/Web Components/静态站点。前端集成体验明显更好。

开发者体验

  • LikeC4 基于 Langium 的 LSP,补全、跳转、实时校验开箱即用;npm/pnpm 安装,Vite 插件,天然融入前端工程链。这是它对"现代 JS/TS 团队"最有杀伤力的地方。

一句话选型建议:README 里顺手一张流程图,用 Mermaid;严肃的、需要长期活着的、多视角的系统架构,且团队是 JS/TS 技术栈、重视 DX 和可嵌入性,选 LikeC4;已经深度绑定 Structurizr 生态或强依赖其云端协作的,继续用 Structurizr 也没问题。它们不是你死我活,是覆盖不同规模和场景。

五、落地实战:把 LikeC4 接进 CI,让架构漂移变成一次红色的 CI

工具再好,不进 CI 就是玩具。真正让"架构永远最新"从口号变成机制的,是把校验和生成塞进流水线。下面是一套可以直接抄的落地方案。

5.1 项目结构约定

docs/
  architecture/
    spec.c4          # specification,全局只此一份
    model.c4         # model,系统骨架
    views.c4         # views,各类镜头
package.json
.github/workflows/architecture.yml

把架构文件放进代码仓库,和代码同生共死。这是"架构即代码"的物理前提。

5.2 本地开发脚本

{
  "scripts": {
    "arch:dev": "likec4 start docs/architecture",
    "arch:check": "likec4 validate docs/architecture",
    "arch:build": "likec4 build -o dist/architecture docs/architecture",
    "arch:export": "likec4 export png -o docs/img docs/architecture"
  },
  "devDependencies": {
    "likec4": "^1.x"
  }
}

arch:dev 起本地热重载,写模型的时候开着它,改一行图变一次。arch:check 是 CI 要用的校验命令。

5.3 CI:把架构校验变成一道质量门

name: Architecture
on:
  pull_request:
    paths:
      - 'docs/architecture/**'
      - '.github/workflows/architecture.yml'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      # 1. 语义校验:悬空引用、命名冲突、非法关系 → 直接 fail
      - name: Validate architecture
        run: npm run arch:check
      # 2. 生成静态站点,作为构建产物
      - name: Build architecture site
        run: npm run arch:build
      - uses: actions/upload-artifact@v4
        with:
          name: architecture-site
          path: dist/architecture

这套流程的关键收益:当有人改了一个服务的依赖,却忘了更新架构模型,likec4 validate 会因为悬空引用或不一致而让 CI 变红。 架构漂移从"没人发现"变成"合不进主干"。这就是反馈回路——和代码同级别的反馈回路——终于被建立起来了。

5.4 进阶:架构 diff 作为 PR 评论

更狠一点的团队,可以在 PR 里对比 base 分支和当前分支的架构,把"新增了哪些依赖、删除了哪些组件"作为机器人评论贴出来。因为架构是结构化数据,diff 是可计算的——这是像素图永远做不到的事。

# 伪流程:在 base 和 head 分别构建 computed model 的 JSON,然后 diff
likec4 export json -o /tmp/base.json   # on base branch
likec4 export json -o /tmp/head.json   # on head branch
node scripts/arch-diff.js /tmp/base.json /tmp/head.json  # 输出人类可读 diff

有了这个,架构评审就从"对着两张图找不同"变成"看一段结构化 diff"。评审质量和速度都是量级提升。

六、性能与规模:模型大了会不会崩

有人会担心:几百个组件、上千条关系,工具还扛得住吗?这里给几个工程判断。

  1. 解析是增量的。基于 Langium 的语言服务支持增量解析,你改一个文件不会全量重编译,编辑器里的响应保持流畅。
  2. 视图是切片,不是全量。真正决定单张图渲染压力的是视图 include 了多少节点,而不是整个模型多大。哪怕模型有一千个元素,一张 view 只 include 二十个,渲染的就是二十个。所以规模治理的关键在于视图设计要克制:不要写一个 include * 试图展示整个宇宙,那张图对人对机器都没意义。
  3. 拆文件。model 可以拆成多个 .c4 文件按限界上下文组织,团队分片维护,减少 merge 冲突。
  4. 布局是渲染期成本。节点极多的单图布局会慢,但这是所有自动布局工具的共性,解法同样是"分视图、别把所有东西塞一张图"。

一句话:LikeC4 的规模瓶颈几乎总是"视图设计不克制",而不是"引擎不行"。把大系统拆成一堆聚焦的小视图,是使用它的正确姿势,也恰好是好的架构表达本来就该有的样子。

七、我的实践取舍与几条踩坑提醒

聊点主观的,用下来我认为值得记下的几条:

第一,先立规矩再建模。 specification 这一段团队一定要先坐下来约定:元素类型有哪些、tag 体系怎么定、命名规范是什么。它太自由了,自由到如果没有约定,两个人建出来的模型会像两种语言。把 spec.c4 当成团队的"架构宪法"来 review。

第二,别把它当 draw.io 用。 如果你还在想"我要精确控制这个框在左上角",你就用错工具了。它的哲学是"你描述关系,布局交给我"。想要像素级控制,你会一直和它对抗。接受自动布局,享受"图永远最新"的红利。

第三,动态视图是被低估的宝藏。 很多人只用静态图,其实 dynamic view 描述关键业务流程(下单、支付、鉴权)的时序,比一堆文字流程说明清晰十倍,而且因为它只能引用真实存在的关系,它天然和系统保持一致。

第四,警惕 include * 上瘾。 全景图偶尔看看可以,但如果你所有视图都 include *,你就退化回了那张没人看得懂的大蜘蛛网。好的视图是有主题的:这张图回答一个具体问题("谁在读数据库""一次结账经过哪些服务")。

第五,把它接进 AI 工作流。 既然它 MCP-friendly,就别浪费。让 Agent 能查询架构,代码评审、影响分析、新人 onboarding 都能借力。这是像素图给不了的能力。

八、总结与展望

回到开头那张 架构图_v3_final_最终版(1).png。它烂掉的根本原因,不是画图的人偷懒,而是它所在的系统没有反馈回路。LikeC4 做的事,本质上就是给架构描述装上和代码一样的反馈回路:

  • 语言层:Langium 撑起真正的编译器前端,写错当场报错。
  • 模型层:单一事实源,一处声明多视图投影,图之间不再各自漂移。
  • 视图层:谓词驱动、动态时序,图自动跟随模型变化。
  • 布局层:自动布局,模型一变图自动重排,人不用回去挪框。
  • 分发层:一份源,产出静态站点 / React 组件 / 图片,处处可嵌。
  • AI 层:MCP 暴露结构化架构,让机器也能查询你的系统。
  • 工程层:接进 CI,架构漂移变成一次红色的构建,从"没人发现"变成"合不进主干"。

它不是完美的。自动布局意味着你放弃了像素级控制;DSL 的高自由度意味着团队必须自律立规矩;它的生态和 Structurizr 相比还年轻。但它指向的方向是对的:架构不该是给人看的一次性快照,而该是给人和机器都能查询、能校验、能演进的活文本。

如果你的团队是 JS/TS 技术栈、受够了过时的架构图、又开始把 AI 塞进研发流程,LikeC4 非常值得花一个下午认真试一次。把它接进 CI 的那一刻,你会第一次拥有一张"永远不会骗你"的架构图——而这,可能是很多团队十年都没做到的事。

架构即代码不是一句口号,它是给"图会烂"这个古老问题的一个工程解。LikeC4 把这个解做得足够现代、足够好用,值得每个还在手动维护架构图的团队严肃看待。

推荐文章

一个简单的html卡片元素代码
2024-11-18 18:14:27 +0800 CST
Golang 中你应该知道的 noCopy 策略
2024-11-19 05:40:53 +0800 CST
CSS 特效与资源推荐
2024-11-19 00:43:31 +0800 CST
windows下mysql使用source导入数据
2024-11-17 05:03:50 +0800 CST
为什么大厂也无法避免写出Bug?
2024-11-19 10:03:23 +0800 CST
一些好玩且实用的开源AI工具
2024-11-19 09:31:57 +0800 CST
程序员茄子在线接单