Skip to main content

BLOGE 0.9.8-RC1 在线版 · 事实校验 2026-09-15 · English

第 1 章 —— BLOGE 是什么,以及它为什么存在

本章建立对 BLOGE 是什么它为什么存在,以及 它最核心的结构特征 的整体认识。


学习目标

  1. 用一句尽量少术语的话解释 BLOGE 是什么。
  2. 说出让 BLOGE 区别于临时拼装编排代码的三个特性:可见有结构有韧性
  3. 从概念层面解释什么是 graph —— 由业务步骤组成、数据流与控制流都显式可见的有向无环图。
  4. 解释为什么并发、重试和失败传播应该由引擎负责,而不是由业务开发者手写在服务代码里。

前置条件

  • 具备基础 Java 知识(不需要很资深)。
  • 已安装可用的 JDK 25+ 与 Maven 3.6+ (参见入门指南)。

源示例

文件展示内容
order-process.bloge一个完整的订单处理教学示例:包含扇出、汇合、分支与韧性策略
bff-dashboard.bloge一个 BFF 聚合图:五个并行拉取节点,带有超时、重试和降级

30 秒认识 BLOGE

从整体上看,BLOGE 是一个面向业务工作流的编排引擎。它并不取代业务代码本身,而是把“步骤之间如何依赖、哪些步骤可以并行、失败后如何处理”这类运行逻辑从零散实现中提取出来,组织成一个显式的 graph。

BLOGE 的定义

BLOGE 是一个 Java 优先的编排引擎。它把业务工作流表示成由 node 和 dependency 组成的 graph,并由 engine 负责执行顺序、并行、重试、超时、分支、等待和恢复,因此原本散落在 service 代码中的运行逻辑被收束为一套显式的结构。

BLOGE 从 DAG 图模型进一步扩展到更高级的编排模式:多轮 session (bloge-session-ext + bloge-session-durable)、显式状态机(bloge-state-ext

  • bloge-state-durable),以及 LLM 驱动的 agent 循环与工具调度 (bloge-agent-ext)。这些能力在后续章节中讲解——当前阶段,graph 模型是一 切的基础。

BLOGEBiz Logic Orchestration Graph Engine 的缩写。这个名称本身已经给出了系统的基本边界:它面向 业务逻辑,处理的是 编排 问题,以 graph 作为执行模型,并由 engine 负责运行。

在这个意义上,BLOGE 既不是单纯的 DSL,也不是单纯的流程图工具。它同时包含工作流模型与执行引擎;流程的形状、依赖关系和失败策略被声明出来之后,引擎按此完成调度、并发控制和韧性处理。

本书用来理解 BLOGE 的三个设计特征

本书使用三个设计特征比较 BLOGE 与临时拼装的编排代码。它们分别对应可读性、执行组织方式和运行期行为的声明方式,是帮助理解设计取向的框架,不是独立的性能或适用性结论。

  1. 可见 —— 流程本身以 graph 的形式直接呈现出来,业务路径、分支位置和上下游关系都可以在模型中看到,而不需要通过阅读多层 service 代码来还原。
  2. 有结构 —— 依赖关系属于工作流模型的一部分。哪些 node 可以并行、哪些 node 必须等待,并不由代码书写顺序决定,而是由 graph 中声明的依赖关系决定。
  3. 有韧性 —— 重试、超时、降级、等待和持久进度等运行期策略直接附着在工作流上,因此失败处理也成为模型的一部分,而不是散落在各处的临时胶水逻辑。

BLOGE 不是什么

明确 BLOGE 的边界,同样有助于理解它的定位。下面三种看法都不准确,因为它们都只抓住了表面形式,而没有抓住它作为编排模型与执行引擎的整体性质。

  1. 它不是顺序代码的另一层外壳。 如果只是把一串方法调用换成另一种写法,执行关系仍然主要由书写顺序决定;而在 BLOGE 里,真正决定执行形状的是 graph 中声明出来的依赖关系。
  2. 它不是单纯的流程图工具。 graph 在这里不是静态说明图,而是可被引擎读取、调度和执行的工作流模型。
  3. 它不是要求业务团队手工补齐调度与恢复机制。 并发、重试、超时、降级和等待等运行期行为不需要在每个业务步骤外层重新拼装,而是由引擎依据模型统一处理。

最简洁的心智捷径可以记成一句话:BLOGE 把工作流意图变成显式 graph,再把运行层面的负担交给引擎。


为什么不是手写编排

只知道 BLOGE 是什么还不够,你还需要知道它为什么值得存在。真正的问题不是“能不能把流程写在 service 里”,而是当流程越来越长、越来越依赖外部系统、越来越需要恢复和排障时,手写编排会迅速失控。

为什么这很重要

很多业务系统一开始都很简单:

1. 取用户
2. 取商品
3. 计算价格
4. 做授信检查
5. 创建订单(或者拒绝)

在普通服务代码里,你通常会把它写成顺序方法调用。这样一开始没问题——直到问题出现:

  • 步骤 1 和步骤 2 本来互不依赖,却被顺序执行,平白浪费总耗时。
  • 步骤 4 可能出现瞬时失败,于是你开始在业务代码里写重试循环,基础设施逻辑和领域逻辑缠到一起。
  • 没人能“看见”整个流程。 一旦线上出问题,排障的人必须先读代码,才能还原到底发生了什么。

这正是 BLOGE 存在的原因。当你先明确 BLOGE 是一个“执行显式 graph 的引擎”之后,这个价值就更容易理解:你声明流程的形状——谁依赖谁、失败时怎么办——然后把调度、并发和韧性策略交给引擎处理。


心智模型

把 BLOGE graph 想成一张交给引擎的流程卡片

Diagram: 01-why-bloge figure 1

每个方框都是一个 node,每条箭头都是一个 dependency。引擎读到这张卡片后,就能知道:

  • fetchUserfetchProducts 彼此没有依赖 → 可以并行运行
  • calcPrice 同时依赖两者 → 必须等两边都完成后再启动
  • checkCredit 的输出驱动一个 branch —— 引擎根据结果选择正确的下游节点。

你描述的是 谁依赖谁;引擎决定的是 什么时候运行谁


第一个可运行示例

下面是从 order-process.bloge 里摘出的一个简化片段:

graph orderProcess {

node fetchUser : FetchUserOperator {
input { userId = ctx.userId }
timeout = 3s
retry = { attempts: 2, backoff: 200ms, strategy: exponential }
}

node fetchProducts : FetchProductsOperator {
input { productIds = ctx.productIds }
timeout = 5s
}

node calcPrice : CalcPriceOperator {
depends_on = [fetchUser, fetchProducts]
input {
user = fetchUser.output
products = fetchProducts.output
}
}

node checkCredit : CreditCheckOperator {
depends_on = [fetchUser, calcPrice]
input {
userId = fetchUser.output.id
amount = calcPrice.output.total
}
retry = { attempts: 3, backoff: 100ms, strategy: jitter }
fallback = { approved: false, reason: "credit service unavailable" }
}

branch on checkCredit.output.approved {
true -> createOrder
false -> rejectOrder
}
}

按顺序读这段 DSL:

  1. 两个互不依赖的拉取节点 —— 引擎会并发执行它们。
  2. 一个计算节点 同时引用两个上游输出 —— 因为出现了 fetchUser.outputfetchProducts.output,依赖关系会被自动推断出来。
  3. 一个带重试和降级的授信检查节点 —— 韧性策略直接声明在节点上。
  4. 一个 branch —— 它不是写在 Java 代码里的 if,而是 graph 里的第一类路由结构。

关键认识: 你没有手写线程池,没有手写重试循环,也没有在业务代码里堆 try/catch 去兜底。引擎负责这些事。


跟着一笔订单:移动责任,而不是移动方法

设想 OrderService.placeOrder() 最初只有五次清楚的方法调用。半年后, 它还要创建异步任务、重试信用检查、给库存查询加超时、捕获多种异常、 记录指标,并决定创建还是拒绝订单。这个方法依然“能跑”,但业务故事和 所有运行机制已经挤在同一个责任边界里。

图:从方法到 graph 的责任地图

把图读成一次责任迁移

BLOGE 的价值不是给方法调用画几个方框,而是让每个问题都有一个可见的 owner:

问题可见 owner
有哪些工作?fetchUsercalcPricecreateOrder 等 node
哪些工作必须先完成?依赖边
瞬时失败后怎么办?node 上的 retry 与 timeout 策略
哪条业务路径没有被选中?结果状态 SKIPPED
业务操作实际上做了什么?Operator 输出与外部证据

这种拆分会直接改变评审方式。增加库存检查,改的是 graph 形状;修改计价 公式,改的是 CalcPriceOperator;调整重试间隔,改的是 node 策略。评审者 不必再从一个控制流方法里重新发现三种不同决策。

先记住第一条边界

显式 graph 只能让已经声明的编排关系可检查;它不能证明 Operator 扣款 金额正确,也不能证明流程满足业务需求。第 18 章与第 23—32 章会逐层补上 更强证据。此时只要抓住一个具体收益:执行形状不再藏在偶然形成的 Java 控制流里。

模块地图 —— 现在你已经知道为什么要看它

订单故事给四层模块各自找到了工作:核心负责运行 graph;编写工具让声明 可读;扩展增加更大的交互模型;运行时集成保留并观察 execution。

图:BLOGE 模块地图

第 1 层 —— 核心

模块作用
bloge-coregraph model、调度器、listener/interceptor API 与 GraphEngine
bloge-runtime-spiexecution、checkpoint、wait、timer、lease、audit 与 inbox store 合同

第 2 层 —— 编写

模块作用
bloge-dsl.bloge parser、compiler 与 code generator
bloge-lang / bloge-lsp共享语言模型与编辑器诊断
bloge-lint / bloge-studio可复现规则与可视化 graph 编写
bloge-maven-pluginOperator 元数据生成

第 3 层 —— 扩展

模块作用
bloge-session-ext / bloge-state-ext多轮 session 与显式状态机
bloge-agent-ext具有能力边界的 model/tool loop
bloge-event-journal只追加的 execution 与 agent 事件

第 4 层 —— 运行时与集成

模块作用
bloge-durable / bloge-durable-mybatis恢复与持久执行状态
bloge-spring / bloge-spring-webBoot wiring、Web 错误与运维控制台
bloge-metrics-otel / bloge-dispatch-kafkatelemetry 与远端 worker 派发
bloge-test / bloge-verificationgraph 测试与业务正确性证据

先使用 bloge-corebloge-dsl。只有当故事走到对应问题时才增加一层; 后面的章节会在这些时刻回到这张地图。


拆开来看

概念含义
graph工作流容器——一个 DAG
node一个处理步骤,会执行某个 operator
input { … }把上游输出和上下文值装配为当前节点的输入
ctx.userId来自 graph context 的值——执行 graph 时传入的初始数据
fetchUser.output上游节点输出;只要引用它,就会形成隐式依赖
timeout节点的执行时限
retry失败时自动重试,并带退避策略
fallback在重试仍失败时使用的静态降级结果
branch on基于节点输出做条件路由,只激活其中一条下游路径

常见陷阱

❌ 把 node 当成普通函数调用来理解

// 不要这样想:
User user = fetchUser(ctx.getUserId());
List<Product> products = fetchProducts(ctx.getProductIds());
Price price = calcPrice(user, products);

在命令式代码里,代码的顺序 就是执行顺序。在 BLOGE 里,依赖图 才是执行顺序。如果两个节点互相不引用对方输出,引擎就有自由把它们并发起来。

要用依赖思维,而不是顺序思维。


引导式重写

看看 bff-dashboard.bloge 这个例子。它为一个仪表盘并行拉取五类数据:

graph bffDashboard {

node fetchProfile : FetchProfileOperator {
input { userId = ctx.userId }
timeout = 2s
retry = { attempts: 1, backoff: 100ms }
}

node fetchOrders : FetchOrdersOperator {
input { userId = ctx.userId }
timeout = 3s
fallback = { recentOrderIds: [], totalOrders: 0 }
}

// … fetchRecommendations, fetchNotifications, fetchLoyalty …

node aggregate : AggregateOperator {
depends_on = [fetchProfile, fetchOrders, fetchRecommendations, fetchNotifications, fetchLoyalty]
input {
profile = fetchProfile.output
orders = fetchOrders.output
recommendations = fetchRecommendations.output
notifications = fetchNotifications.output
loyalty = fetchLoyalty.output
}
}
}

可以思考下面几个问题:

  1. 有多少个节点会并行运行?(答案:五个 fetch 节点都会并行,因为它们之间没有依赖。)
  2. 如果 fetchOrders 失败或超时会发生什么?(答案:fetchOrders 没有重试策略,只有 fallback;因此会直接使用 { recentOrderIds: [], totalOrders: 0 } 这份降级结果,graph 仍能继续执行。)
  3. 如果你新增第六个 fetch 节点,并让它依赖 fetchProfile.output,它还能与其他 fetch 节点一起并行吗?(答案:不能,它必须先等 fetchProfile 完成。)

脑力检查

  1. 让 BLOGE graph 区别于临时编排代码的三个核心特征是什么?(可见、有结构、有韧性。)
  2. 两个节点是否并发执行,是由开发者逐行控制,还是由引擎决定?(由引擎根据依赖图决定。)
  3. order-process 例子里,如果 checkCredit 在三次重试后仍然失败,会发生什么?(会使用 fallback 值:{ approved: false, reason: "credit service unavailable" }。)

练习

打开 order-process.bloge

  1. 新增一个 fetchInventory 节点,让它与 fetchUserfetchProducts 并行执行,并给它 timeout = 2s
  2. 把它的输出接入 calcPrice,为 calcPrice 增加一个新的输入绑定。
  3. 预测新的执行形状:哪些节点会并行?哪些节点会等待?

这里暂时不必急着编译,重点在于先把“如何读 DSL、如何改 DSL”的基本方式看清楚。真正的运行过程放在第 2 章


实验验收卡

  • 预期与观察: 依赖与韧性策略在 DSL 中可见,读者能标注执行形状。
  • 失败与恢复: 让节点依赖不存在的输出;撤销错误边并重画 DAG。
  • 证明边界: 只证明设计意图可审查,不证明可编译、可运行或业务正确。
  • 练习合同: 订单 DSL;只加 inventory 节点和一条绑定;交付依赖图;每条边有来源且无环即停止。

回顾

  • BLOGE 用一个可见、有结构、有韧性的 graph 替代手写编排。
  • graph 是由 node 组成的 DAG,每个 node 运行一个 operator
  • 引擎根据依赖关系(显式声明,或通过 .output 推断)来调度节点。
  • 韧性策略(timeout、retry、fallback)声明在节点上,而不是埋在业务代码里。
  • branch 根据节点输出进行控制流路由。

下一步

第 2 章 —— 你的第一个 Graph继续展开最小 graph 的编写、编译与执行过程。


参考链接

Coding Agent: Open the versioned task guide.