构建可执行链以理解可组合系统
Source: Dev.to
请提供您希望翻译的完整文本(除代码块和 URL 之外),我将把它翻译成简体中文并保留原有的 Markdown 格式。
Source: …
灵感
在探索像 LangChain 这样的框架时,我注意到一个反复出现的概念:与其编写单块的逻辑,执行被拆分为可组合的步骤,这些步骤可以链式调用、重复使用并重新排列。
本文 并非 讨论 LangChain 本身,而是通过从头构建一个最小的 可执行链 抽象,来理解其背后的核心思想。
为什么要这样做?
在没有执行链的思维模型时工作,常常会遇到以下痛点:
- 深度嵌套的条件语句
- 硬编码的执行顺序
- 单一的“全能”方法
结果是,整个操作链变得脆弱、难以测试且难以重构。
如果我们把每一步都视为只做一件事的单元,就能得到链式结构的基本构建块。
有了这种思维模型,我们就可以设计支持它的最小抽象。
什么是可执行链?
正如其名称所示,可执行链是一系列小而专注的逻辑单元,这些单元可以被排列或重新排列,以完成更大的工作单元。
下面是一个简单的 订单处理 链示例。
每个步骤处理一个明确的过程,链条按顺序执行。
可执行单元 是指:
- 接收输入
- 执行工作
- 返回输出
并且它具备:
- Composable(可组合)
- Testable(可测试)
- Predictable(可预测)
设计核心抽象
Executable.java
@FunctionalInterface
public interface Executable {
ChainAction execute(ChainContext context) throws ChainException;
}
这是链的构建块。我们把业务逻辑封装在 execute 方法中,并使用 ChainContext 来跟踪链的当前状态。该方法返回一个 ChainAction,用于决定链是继续执行下一步还是停止执行。
返回 ChainAction 使控制流显式化,并让各个可执行单元不必了解更大的链结构。
ChainAction.java
public enum ChainAction {
CONTINUE,
STOP
}
一个不言自明的枚举,声明了步骤完成后可能的两种操作:
- CONTINUE – 继续执行下一步
- STOP – 按原样停止当前链
ChainException.java
public class ChainException extends RuntimeException {
// Other fields can be added for metadata
}
自定义的未检查异常,必要时可以添加额外的元数据字段。
ChainContext.java
public class ChainContext {
private final Map values;
public ChainContext() {
this.values = new HashMap<>();
}
public <T> void setValue(String key, T value) {
values.put(key, value);
}
public Object getValue(String key) {
return values.get(key);
}
@SuppressWarnings("unchecked")
public <T> T getValue(String key, Class<T> clazz) {
Objects.requireNonNull(clazz);
return (T) values.get(key);
}
}
该类概括了进入 Executable 的内容以及从中返回的内容。
我们通过设置键‑值对来传递输入,同样方式处理输出。
注意: 使用
Map是在类型安全与灵活性之间的取舍。这是为了保持抽象的简洁和通用性而作出的刻意选择。
使用 Chain.java 控制一切
public class Chain {
private final String name;
private final List<Executable> executables;
private ChainContext context;
private Chain(String name) {
this.name = name;
this.executables = new ArrayList<>();
this.context = new ChainContext();
}
public static Chain of(String name) {
Objects.requireNonNull(name);
return new Chain(name);
}
public Chain next(Executable executable) {
executables.add(executable);
return this;
}
public Chain context(ChainContext context) {
this.context = context;
return this;
}
public void run() throws ChainException {
System.out.println("Executing Chain : " + name);
for (var e : executables) {
ChainAction state = e.execute(context);
if (state == ChainAction.STOP) break;
}
}
public void printChain() {
var s = executables.stream()
.map(e -> e.getClass().getSimpleName())
.collect(Collectors.joining(" --> "));
System.out.println(s);
}
}
细分
name– 链的标识符。executables– 步骤的有序列表。- Fluent API –
of(String)、next(Executable)和context(ChainContext)让您以可读的方式构建链。 run()– 遍历 executables,遵循每个步骤返回的ChainAction。printChain()– 将链可视化为StepA --> StepB --> StepC。
请注意,每个 Executable 对前后发生的事情一无所知。这种松耦合使得链具备 可组合、可测试 和 易于重排 的特性。
综合示例
定义常量类
public class GenericConstants {
public static final String ORDER_ID = "orderId";
public static final String CURRENT_STATE = "currentState";
}
定义步骤
public class ValidateOrder implements Executable {
@Override
public ChainAction execute(ChainContext context) throws ChainException {
System.out.println("Executing ValidateOrder");
context.setValue(GenericConstants.CURRENT_STATE, "ValidateOrder Done!");
return ChainAction.CONTINUE;
}
}
public class CheckInventory implements Executable {
@Override
public ChainAction execute(ChainContext context) throws ChainException {
System.out.println("Executing CheckInventory");
System.out.println(" Previous State : " + context.getValue(GenericConstants.CURRENT_STATE));
context.setValue(GenericConstants.CURRENT_STATE, "CheckInventory Done!");
return ChainAction.CONTINUE;
}
}
public class ReserveStock implements Executable {
@Override
public ChainAction execute(ChainContext context) throws ChainException {
System.out.println("Executing ReserveStock");
System.out.println(" Previous State : " + context.getValue(GenericConstants.CURRENT_STATE));
context.setValue(GenericConstants.CURRENT_STATE, "ReserveStock Done!");
return ChainAction.CONTINUE;
}
}
public class TakePayment implements Executable {
@Override
public ChainAction execute(ChainContext context) throws ChainException {
System.out.println("Executing TakePayment");
System.out.println(" Previous State : " + context.getValue(GenericConstants.CURRENT_STATE));
context.setValue(GenericConstants.CURRENT_STATE, "TakePayment Done!");
return ChainAction.CONTINUE;
}
}
public class SendNotification implements Executable {
@Override
public ChainAction execute(ChainContext context) throws ChainException {
System.out.println("Executing SendNotification");
System.out.println(" Previous State : " + context.getValue(GenericConstants.CURRENT_STATE));
System.out.println(" Order with Id: " + context.getValue(GenericConstants.ORDER_ID) + " processed!");
context.setValue(GenericConstants.CURRENT_STATE, "SendNotification Done!");
return ChainAction.CONTINUE;
}
}
运行链
public class Main {
public static void main(String[] args) {
Chain chain = Chain.of("order-process-chain")
.next(new ValidateOrder())
.next(new CheckInventory())
.next(new ReserveStock())
.next(new TakePayment())
.next(new SendNotification());
ChainContext context = new ChainContext();
context.setValue(GenericConstants.ORDER_ID, "ORD-123");
chain.context(context);
chain.run();
System.out.println();
chain.printChain();
}
}
/* Output:
Executing Chain : order-process-chain
Executing ValidateOrder
Executing CheckInventory
Previous State : ValidateOrder Done!
Executing ReserveStock
Previous State : CheckInventory Done!
Executing TakePayment
Previous State : ReserveStock Done!
Executing SendNotification
Previous State : TakePayment Done!
Order with Id: ORD-123 processed!
ValidateOrder --> CheckInventory --> ReserveStock --> TakePayment --> SendNotification
*/
注意:
ChainContext用于在链中的 Executables 之间传递数据。
此实现 不 处理的内容
- 其他链操作,例如
SKIP、ROLLBACK、RETRY(这会增加更多功能)。 - 链和可执行对象的生命周期钩子,例如
onFailure()、before()、after()。 - 步骤的并行执行。
- 用于根据名称存储链的 Chain Registry。
最后的话
创建简单的抽象既有趣又有教育意义。它们迫使你面对那些常常隐藏在精致框架背后的权衡取舍。
构建这个可执行链能够清晰地表明,许多现代系统——从工作流引擎到 AI 流水线——都依赖相同的底层思想。一旦你理解了抽象,框架就不再显得神奇。
