构建可执行链以理解可组合系统

发布: (2026年1月19日 GMT+8 01:44)
8 min read
原文: Dev.to

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 APIof(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 之间传递数据。

此实现 处理的内容

  • 其他链操作,例如 SKIPROLLBACKRETRY(这会增加更多功能)。
  • 链和可执行对象的生命周期钩子,例如 onFailure()before()after()
  • 步骤的并行执行。
  • 用于根据名称存储链的 Chain Registry

最后的话

创建简单的抽象既有趣又有教育意义。它们迫使你面对那些常常隐藏在精致框架背后的权衡取舍。

构建这个可执行链能够清晰地表明,许多现代系统——从工作流引擎到 AI 流水线——都依赖相同的底层思想。一旦你理解了抽象,框架就不再显得神奇。

Back to Blog

相关文章

阅读更多 »

Rapg:基于 TUI 的密钥管理器

我们都有这种经历。你加入一个新项目,首先听到的就是:“在 Slack 的置顶消息里查找 .env 文件”。或者你有多个 .env …

技术是赋能者,而非救世主

为什么思考的清晰度比你使用的工具更重要。Technology 常被视为一种魔法开关——只要打开,它就能让一切改善。新的 software,...

踏入 agentic coding

使用 Copilot Agent 的经验 我主要使用 GitHub Copilot 进行 inline edits 和 PR reviews,让我的大脑完成大部分思考。最近我决定 t...