LikeC4 Dynamic Views 完全指南:用 DSL 绘制流程与时序图
【免费下载链接】likec4Visualize, collaborate, and evolve the software architecture with always actual and live diagrams from your code项目地址: https://gitcode.com/GitHub_Trending/li/likec4
动态视图(Dynamic View)是 LikeC4 中用于表达元素之间时间性交互与调用流的视图类型。它把 C4 模型中的静态关系"串"成一条条带方向的步骤,渲染为可动画的流程图(Flow Diagram),也可以切换为 UML 风格的时序图(Sequence Diagram)。本指南以 skills/likec4-dsl/references/dynamic-views.md 为骨架,结合本仓库的语法定义、校验器与测试用例,完整讲解 dynamic view 的语法、流程控制块、步骤属性、导航下钻、时序图变体与常见反模式,读完即可在.c4文件中写出可编译、可校验的复杂动态视图。
语法总览
动态视图写在views { ... }块内,以dynamic view <name> { ... }声明。与静态视图(view)不同,动态视图的核心不是谓词过滤,而是逐条显式书写的步骤(step):
views { dynamic view name { variant sequence // 可选:以 UML 时序图渲染 title "Flow title" description "Flow description" SOURCE -> TARGET "step title" SOURCE <- TARGET "return flow" // 流程控制块(每个都可带可选标题,可任意嵌套) parallel 'optional' { // 也支持:par、opt、loop、break SOURCE_1 -> TARGET_1 "parallel action 1" SOURCE_2 -> TARGET_2 "parallel action 2" } alt 'optional' { // 互斥分支容器 when 'condition' { SOURCE -> TARGET } else { SOURCE -> TARGET } } try 'optional' { SOURCE -> TARGET } catch 'optional' { SOURCE -> TARGET } finally 'optional' { SOURCE -> TARGET } } }从语法定义(like-c4.langium)可以看到,DynamicView由'dynamic' 'view' name=Id组成,其 body 是DynamicViewBody:先是一些视图属性(tags、title、description、variant 等),随后混排steps+=StepStatement与rules+=DynamicViewRule(如include、style、autoLayout)。也就是说,动态视图内部可以同时存在步骤序列与静态视图规则,二者互相补充。
基本步骤
前向步骤
用->表达从源到目标的调用/消息传递,标题会显示在箭头上:
dynamic view checkout-flow { customer -> frontend "opens cart" frontend -> backend "requests checkout" backend -> payment-service "initiates payment" payment-service -> bank "authorizes card" }在语法层面,单个步骤对应 Step:source=ElementRef ('<-' | '->' | '-[rel]->') target=ElementRef,其中->是最常见的无类型箭头。注意元素引用既可以是简单名(customer),也可以是带层级限定名的引用(cloud.ui.mobile),后者在 e2e 用例 model.c4 中大量出现。
返回步骤
用<-表示响应/返回流(语义上是从目标流回源):
dynamic view checkout-flow { customer -> frontend "opens cart" frontend -> backend "requests checkout" frontend <- backend "payment result" // 返回流 }<-表示 response/return 流;- 渲染为虚/点线箭头,具体样式取决于主题;
- 语义上明确指出流向是回到源。
语法中isBackward?='<-'正是为它设计的标记位(like-c4.langium)。校验器对返回步骤还有一个约束:stepSeries检查中,若链式表达式的起点是一个 backward step,会报Invalid chain after backward step(见 validation/dynamic-view.ts)。
链式步骤
把多次跳转写成一个复合表达式,语义上更紧凑:
dynamic view multi-hop { customer -> frontend "request" -> backend "forwards" -> db "query" }语法上这对应StepSeries:source -> target1 -> target2 ...,每次跳转由(dotKind | '->' | '-[kind]->')连接(like-c4.langium)。当提示词明确要求链式语法时,应保持一整条链,而不是拆成多个顶层独立步骤(这也是文档 Quick anti-patterns 第一条)。
跳转局部属性体(hop-local body)
可以把属性体{ ... }只挂在某一次跳转(hop)上:
dynamic view checkout-flow { customer -> frontend -> api { technology 'HTTPS' navigateTo payment-detail } }关键点:属性体只作用于它所在的那一次跳转。除非提示词允许,否则不要把块整体上移到整条链上。
流程控制块
步骤可以被分组进流程控制块(flow-control block)。parallel只是其中一种,并非特例。每个块:
- 在关键字后紧跟一个可选标题(
String),然后包裹一个{ ... }步骤体; - 可以任意深度嵌套——唯一的例外是
parallel/par不能再嵌套在另一个parallel/par内。
块关键字映射表:
| 块 | 关键字 | 含义 |
|---|---|---|
| 并行 | parallel/par | 块内步骤并发执行 |
| 可选 | opt | 可能被跳过的步骤组 |
| 循环 | loop | 会重复执行的步骤组 |
| 中断 | break | 打断外层流程(如退出 loop) |
| 分支容器 | alt | 一组互斥分支的容器 |
| 分支(alt 内) | when/if/else | alt的某一分支(if是when的别名) |
| 异常处理 | try/catch/finally | 正常路径 + 可选错误处理 |
语法上这些块统一建模为SubflowStep(kind: 'opt' | 'par' | 'parallel' | 'loop' | 'when' | 'if' | 'else' | 'break'),见 like-c4.langium。try则是独立的TryStep(TryBlock | CatchBlock | FinallyBlock),通过FinallyBlock→CatchBlock→TryBlock的嵌套链实现try→catch?→finally?的固定顺序。
Parallel ——parallel/par
块内步骤并发执行,常用于一个源扇出到多个目标:
dynamic view fan-out { backend -> cache "write to cache" parallel { backend -> db "save record" backend -> audit-log "log event" backend -> notification-service "send notification" } }par { ... }是parallel { ... }的完全别名;- 渲染为时间对齐的水平布局;
- 不可嵌套:
parallel { parallel { ... } }(或混用par)是校验错误Nested parallel blocks are not allowed;但parallel可以放在opt/loop/try/alt分支内部; - 当一个提示词只要求一次扇出时,把兄弟动作放在同一个
parallel { ... }块里,不要拆成多个单步块。
这一限制在源码中有明确校验:subflowStep检查器判断kind === 'par' || kind === 'parallel'且其容器同为并行块时直接报错(validation/dynamic-view.ts),对应的测试见 views-dynamic-blocks.spec.ts 与 views-dynamic.spec.ts。
Optional ——opt
一组可能被跳过的步骤:
opt 'if not cached' { api -> db 'load and cache' }Loop ——loop
一组会重复执行的步骤:
loop 'until success' { api -> auth 'retry authentication' }Break ——break
打断外层流程(典型场景是退出loop):
loop 'poll for result' { api -> db 'check status' break 'when ready' { api -> web 'return result' } }Alternatives ——alt与when/if/else
alt是互斥分支的容器,其直接子节点必须是分支——when、if或else(各自可带可选标题):
alt { when 'authorized' { app -> api 'requests data' } else 'not authorized' { app -> customer 'shows login' } }if是when的别名,两者都是分支关键字;when/if/else只能出现在alt内部。放在顶层或loop/parallel/opt内是校验错误:"when" alternative branch must be inside "alt";- 把非分支块(
loop、opt、parallel、try)作为alt的直接子节点也是校验错误:"loop" can not be used as an alternative branch。需要嵌套时,把这类块放进某个when/else分支内部。
语法上AltSteps定义为'alt' title? '{' branches+=SubflowStep* '}',而校验器通过isAltSteps(el.$container)判断分支是否处于alt中(validation/dynamic-view.ts),测试覆盖见 views-dynamic-blocks.spec.ts。
Try / catch / finally
建模"正常路径 + 可选错误处理"。catch与finally均可选,但顺序固定:try→catch?→finally?:
try { api -> db 'query' } catch 'on failure' { api -> web 'shows error' } finally { api -> api 'release resources' }try单独、try { } catch { }、try { } finally { }、try { } catch { } finally { }都是合法组合;- 没有前置
try的catch,或finally之后的catch,都是校验错误; try不能作为alt的直接子节点(只有分支可以)——请把它放进when/else分支内。
上述四种合法组合在 views-dynamic-blocks.spec.ts 中有对应测试,非法用例(裸catch、catch置于finally之后、try直接放进alt)也都用toBeInvalid断言验证。
嵌套
除了parallel不能套parallel,其他块可以任意组合嵌套:
dynamic view resilient-sync { variant sequence loop 'until synced' { try { alt { when 'online' { web -> api 'sync changes' } else 'offline' { opt { web -> web 'queue locally' } } } } finally { web <- api 'ack' } } }e2e 仓库中 views.c4 的flow-control-1视图是一个真实的综合样例:alt内含when/else,else里再套loop;随后try { parallel { ... } } catch { ... } finally { ... },再接opt与链式返回步骤,几乎用全了所有控制块。
步骤属性(Step Properties)
给步骤挂一个{ ... }body 可以附加更多元数据:
dynamic view with-notes { customer -> frontend "view product" { title "View Product Detail" technology "HTTPS" description "Customer opens product page" } frontend -> backend "fetch product data" { notes """ Includes: - Product info - Pricing - Available inventory """ } backend -> db "query" { metadata { latency "50ms" cached false } } }步骤体内可用属性:
title— 备用或更长的标题technology— 所用技术/协议description— 详细描述notes— Markdown 格式的注释metadata— 自定义键值对
语法上,步骤的custom=CustomRelationProperties?挂的是关系级属性集(like-c4.langium),其中包含navigateTo、字符串属性、notation、notes、关系样式属性与multiple。测试 views-dynamic.spec.ts 验证了步骤级description、color、notation、title、notes(含多行 Markdown)均合法。
导航与下钻(Navigation & Drill-down)
从步骤链接到另一个视图:
dynamic view high-level { customer -> frontend "browse" frontend -> backend "request data" { navigateTo>dynamic view checkout-sequence { variant sequence // 切换到时序图渲染 customer -> frontend "click checkout" frontend -> backend "POST /checkout" backend -> payment-service "charge card" payment-service <- bank "authorization" backend <- payment-service "charge approved" frontend <- backend "200 OK" customer <- frontend "show confirmation" }variant sequence告诉 LikeC4 按时序图渲染;- 时间线自上而下流动(而不是流程图的自左向右);
- 参与者(actor)成为 lifeline;返回箭头为虚线。
语法上由DynamicViewDisplayVariantProperty: key='variant' value=DynamicViewDisplayVariantValue定义(like-c4.langium),且variant属性只能在动态视图内部出现——校验器dynamicViewDisplayVariant会检查其容器必须是DynamicViewBody,且取值只能是diagram或sequence(validation/dynamic-view.ts)。e2e 中的 model.c4 有variant sequence的真实用例。
流程图与时序图渲染对比
| 维度 | 流程图(Flow Diagram) | 时序图(variant sequence) |
|---|---|---|
| 布局 | 自左向右或自上而下 | 自上而下的 lifeline |
| 返回箭头 | 样式随主题变化 | 标准虚线 |
| 并行的表现 | 水平对齐 | 基于时间、带间距 |
| 最佳场景 | 系统交互 | 消息交换协议 |
返回箭头模式(Response Arrow Patterns)
当提示词要求"返回箭头"时,优先用<-步骤表达响应,而不是凭空发明额外的前向步骤:
dynamic view checkout-flow { customer -> frontend -> api customer <- frontend <- api // 响应链优先用 <- 保持对称 }不要写成:
dynamic view checkout-flow { customer -> frontend -> api api -> frontend "returns" // 错误:不是返回箭头(缺少对称性) frontend -> customer "returns" }常见模式
扇出 + 返回
dynamic view fan-out-return { backend -> cache "check" backend -> db "if miss" backend <- db "result" backend -> client "send" }顺序:先发给多个目标,再各自收集(结果不同时)响应。注意这里"check 后 if miss 再 query"如果目标是表达并发,应改用parallel;文档 dynamic-views.md 中with-cache示例展示了frontend -> cache "check"/cache -> backend "if miss"的缓存旁路写法。
管道 / 分阶段流程
dynamic view>dynamic view payment-decision { customer -> frontend "enter amount" frontend -> backend "validate" alt { when 'within limit' { backend -> payment-service "charge" backend <- payment-service "result" } else 'over limit' { backend -> frontend "rejected" } } }- 需要"无 else 的 if"(一组可跳过的步骤)时用
opt;需要成功 vs 失败路径时用try/catch; - 当不值得写一个完整分支块时,步骤上的
notes仍是记录决策逻辑的轻量方式。
常见反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
嵌套parallel { parallel { ... } } | 报Nested parallel blocks are not allowed | 把所有并发步骤放进一个parallel {} |
when/if/else出现在alt之外 | 报alternative branch must be inside "alt" | 用alt { ... }包住分支 |
loop/opt/parallel/try直接作为alt子节点 | 报can not be used as an alternative branch | 把它们嵌套在when/else分支内部 |
没有try就写catch,或finally之后写catch | try/catch/finally 顺序非法 | 保持try→catch?→finally? |
| 单个视图步骤过多 | 图变得不可读 | 用navigateTo拆分成子视图 |
用<-表达并列动作 | 语义上暗示返回,破坏时序语义 | 用前向->或parallel分组 |
需要 UML 时序图却忘了variant sequence | 渲染结果不对 | 需要时序图时加上variant sequence |
动态视图中的谓词过滤
动态视图不使用谓词来生成或过滤交互步骤——步骤必须逐条显式列出。
但仍可以用普通的 include 谓词添加不参与步骤的上下文元素:
dynamic view critical-flow { frontend -> api "request" api -> db "query" include cloud.* where tag is #critical include cloud.* where metadata.region is "eu" } dynamic view with-cache { // 带缓存层的备选流程 frontend -> cache "check" cache -> backend "if miss" }语法上这对应DynamicViewIncludePredicate: 'include' exprs=Expressions(like-c4.langium),与静态视图的include/exclude谓词规则(ViewRulePredicate)不同——动态视图只支持include方向。e2e 用例 model.c4 与 views.c4 中都有include+ 谓词/样式配合步骤使用的实例。
典型应用场景
文档 dynamic-views.md 列出的常见场景:
- 认证流程— customer → login → auth-service → database
- CQRS 模式— command → service → write-db(query-handler → read-db 用
parallel) - Webhook 回调— external-system → api(内部
parallel,配合navigateTo service-webhook-handler下钻) - Pub/Sub 流— publisher → message-bus → consumer(多个订阅者用
parallel)
写作时的精确性要点:
- 提示词要求链式语法时,保持一条复合链,不要改写成多个顶层独立步骤;
- 提示词明确要求返回箭头时,用
<-而不是前向箭头; - 一次并行扇出只用一个
parallel块,不要拆成多个; - 不要省略提示词要求的 hop 局部 token(如
technology、navigateTo); when/if/else只能直接放在alt内;loop/opt/parallel/try不能作为alt的直接子节点;parallel内不能再嵌套parallel(唯一不可嵌套的块);- 严格遵守
try→catch?→finally?的顺序。
底层实现参考
想深入理解 dynamic view 的编译与校验链路,可以在本仓库中继续阅读:
- 语法定义:packages/language-server/src/like-c4.langium(
DynamicView声明)、L648-L812(步骤与流程控制块语法); - 校验逻辑:packages/language-server/src/validation/dynamic-view.ts(并行嵌套、alt 分支归属、try/catch/finally 顺序、variant 取值);
- 单元测试:packages/language-server/src/tests/views-dynamic.spec.ts、views-dynamic-blocks.spec.ts;
- 视图计算:packages/core/src/compute-view/dynamic-view/compute.ts 与 computeFlow.ts;
- 端到端样例:e2e/src/likec4/model.c4(含
parallel、opt、variant sequence、navigateTo)、e2e/src/likec4/views.c4(alt/loop/try/parallel综合嵌套)。
掌握了这些语法、校验规则与反模式,你就能在 LikeC4 中稳定地产出既符合语言规范、又能在编辑器(LSP)中零报错的动态视图与时序图。
【免费下载链接】likec4Visualize, collaborate, and evolve the software architecture with always actual and live diagrams from your code项目地址: https://gitcode.com/GitHub_Trending/li/likec4
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考