什么是工作流引擎?
工作流引擎是自动化业务流程的核心调度系统,通过定义任务执行顺序、条件判断和异常处理规则,实现:
- 跨系统任务编排(如订单处理→物流通知→发票生成)
- 人工审批节点嵌入(如请假流程需经理确认)
- 状态追踪与可视化监控(甘特图展示流程进度)
- 错误重试与补偿机制(自动重试失败的网络请求)
典型应用场景:AI任务编排、信贷审批、供应链协同
为什么要使用工作流引擎?
- 解耦业务逻辑:通过将“做什么”与“怎么做”相分离,使得流程变更无需深入修改核心代码,降低了系统维护的复杂度和风险,提高了系统的灵活性和可维护性。
- 统一思维共识:借助直观的流程设计器,业务人员可以轻松理解和修改流程,从而在不同部门之间搭建起沟通的桥梁,促进高效的协作。
- 追踪任务执行:实时监控跨服务流程实例的执行状态,以图形化的方式展示流程进度和各任务节点的执行情况,帮助及时发现流程中的瓶颈和异常,保障业务流程的顺利进行。
Inngest:轻量级事件驱动型工作流引擎的代表
核心定位:Inngest 是一款基于事件驱动的轻量级工作流引擎,其核心设计理念是零基础设施依赖。开发者无需搭建独立的工作流服务,直接通过 SDK 在现有代码库中定义函数(Function)并声明事件触发规则,即可实现复杂逻辑编排。
技术特性:
- 事件驱动架构:通过发送事件(Event)触发函数执行,天然支持异步任务处理(如订单超时检测、用户行为追踪)。
- 自动容错机制:内置函数执行失败后的自动重试、并发控制(防止资源过载)和状态持久化(系统崩溃后恢复执行)。
- 开发友好性:提供本地开发服务器,支持 TypeScript 定义函数逻辑,且函数可独立测试与调试。
Nest.js 集成 Inngest
安装依赖
pnpm add inngest
创建Inngest Module
目录结构如下
./src/shared/modules/inngest
├── index.ts
├── inngest-module-options.interface.ts
├── inngest.decorator.ts
├── inngest.module-definition.ts
├── inngest.module.ts
└── inngest.service.ts
inngest-module-options.interface.ts
import { ClientOptions } from 'inngest';
export interface InngestModuleOptions {
inngest: ClientOptions;
path?: string;
}
inngest.module-definition.ts
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { InngestModuleOptions } from './inngest-module-options.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
new ConfigurableModuleBuilder<InngestModuleOptions>()
.setExtras(
{
isGlobal: true,
},
(definition, extras) => ({
...definition,
global: extras.isGlobal,
}),
)
.build();
inngest.decorator.ts
import { DiscoveryService } from '@nestjs/core';
import { Inngest } from 'inngest';
export const InngestFunction = DiscoveryService.createDecorator<{
config: Parameters<Inngest['createFunction']>[0];
trigger: Parameters<Inngest['createFunction']>[1];
}>();
inngest.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
import { Inngest } from 'inngest';
import { InngestModuleOptions } from './inngest-module-options.interface';
import { InngestFunction } from './inngest.decorator';
import { MODULE_OPTIONS_TOKEN } from './inngest.module-definition';
@Injectable()
export class InngestService extends Inngest {
private readonly functions: ReturnType<Inngest['createFunction']>[] = [];
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
private readonly moduleOptions: InngestModuleOptions,
private readonly discoveryService: DiscoveryService,
private readonly metadataScanner: MetadataScanner,
) {
if (!moduleOptions) {
throw new Error('InngestModuleOptions is not defined');
}
super(moduleOptions.inngest);
}
getPath() {
return this.moduleOptions.path;
}
getFunctions() {
if (this.functions.length > 0) {
return this.functions;
}
const providers = this.discoveryService.getProviders();
for (const wrapper of providers) {
if (!wrapper.metatype?.prototype) {
continue;
}
const handlers = this.metadataScanner.getAllMethodNames(wrapper.metatype.prototype);
for (const handler of handlers) {
const metadata = this.discoveryService.getMetadataByDecorator(InngestFunction, wrapper, handler);
if (!metadata) {
continue;
}
const { config, trigger } = metadata;
this.functions.push(
this.createFunction(config, trigger, wrapper.instance[handler].bind(wrapper.instance)),
);
}
}
return this.functions;
}
}
inngest.module.ts
import { DynamicModule, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { serve } from 'inngest/express';
import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from './inngest.module-definition';
import { InngestService } from './inngest.service';
@Module({
imports: [DiscoveryModule],
providers: [InngestService],
exports: [InngestService],
})
export class InngestModule extends ConfigurableModuleClass implements NestModule {
constructor(private readonly inngestService: InngestService) {
super();
}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
serve({
client: this.inngestService,
functions: this.inngestService.getFunctions(),
}),
)
.forRoutes(this.inngestService.getPath());
}
static register(options: typeof OPTIONS_TYPE): DynamicModule {
return {
...super.register(options),
};
}
static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
return {
...super.registerAsync(options),
};
}
}
inngest.config.ts
import { InngestModuleOptions } from '@/shared/modules/inngest';
import { registerAs } from '@nestjs/config';
export default registerAs<InngestModuleOptions>('inngest', () => {
return {
path: 'inngest',
inngest: {
id: 'Inngest Mesh',
baseUrl: process.env.INNGEST_BASE_URL,
},
};
});
导入InngestModule
InngestModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => config.get("inngest"),
})
定义流程
import { InngestFunction } from "@/shared/modules/inngest";
import { Injectable } from "@nestjs/common";
@Injectable()
export class TestFlow {
@InngestFunction({
config: {
id: 'activation-email',
},
trigger: {
event: 'app/user.created'
},
})
async test({ event, step }) {
await step.run("send-welcome-email", async () => {
return await sendEmail({ email: event.user.email, template: "welcome" });
});
// 等待一个 "app/post.created" 事件
const postCreated = await step.waitForEvent("wait-for-post-creation", {
event: "app/post.created",
match: "data.user.id", // 字段 "data.user.id" 必须匹配
timeout: "24h", // 最多等待24小时
});
if (!postCreated) {
// 如果没有创建帖子,发送提醒邮件
await step.run("send-reminder-email", async () => {
return await sendEmail({
email: event.user.email,
template: "reminder",
});
});
}
}
}
触发流程
import { InngestService } from "@/shared/modules/inngest";
await this.inngestService.send({
name: "app/user.created",
data: {
id: 123
}
});
React 集成 Inngest 流程设计器
import { PublicEngineAction, Workflow } from "@inngest/workflow-kit";
import { Editor, Provider, Sidebar } from "@inngest/workflow-kit/ui";
import { useReactive } from "ahooks";
import "@inngest/workflow-kit/ui/ui.css";
import "@xyflow/react/dist/style.css";
export const availableActions: PublicEngineAction[] = [
{
kind: "pdf-parse",
name: "PDF文件解析"
},
{
kind: 'markdown-split',
name: 'Markdown文本分割'
},
{
kind: 'embed',
name: '嵌入'
},
{
kind: 'vector-insert',
name: '向量数据库插入'
},
];
export default () => {
const state = useReactive<{
workflow: Workflow
}>({
workflow: {
name: 'PDF文件解析',
description: 'PDF文件解析',
edges: [],
actions: []
}
});
return (
<div className="w-screen h-screen">
<Provider
workflow={state.workflow}
trigger={{
event: {
name: 'upload.pdf',
}
}}
availableActions={availableActions}
onChange={workflow => {
state.workflow = workflow;
}}
>
<Editor>
<Sidebar position="right"></Sidebar>
</Editor>
</Provider>
</div>
);
};
n8n:低代码工作流自动化的瑞士军刀
核心定位:n8n 是一款开源的可视化工作流自动化平台,以低代码为核心理念,支持通过拖放节点(Node)构建复杂流程,同时保留深度定制能力。
技术特性:
- 超 400 种集成节点:覆盖从社交媒体(Twitter、Slack)到企业级应用(SAP、Salesforce)的广泛生态,支持自定义节点开发。
- 双向数据流:不仅支持单向触发-执行流程,还可构建包含循环、条件判断(IF/ELSE)、子流程调用的逻辑网络。
- 企业级扩展性:基于 Docker 容器化部署,支持 Kubernetes 集群,提供审计日志、错误重试策略和基于角色的权限控制(RBAC)。
自定义 n8n
git clone git@github.com:n8n-io/n8n.git
cd n8n
pnpm i && pnpm build
pnpm start
工作原理
graph TD
A[API 服务器] --> B[工作流存储]
B --> C{工作流触发}
C --> D[工作流调度]
D --> E[工作流运行器]
E --> F[中间件处理]
F --> G[执行节点操作]
G --> H[数据处理]
H --> I[节点间数据流动]
I --> J[中间件处理]
J --> K{工作流完成?}
K -->|是| L[返回执行结果]
K -->|否| E
C -->|手动触发| L
C -->|定时触发| L
C -->|外部事件触发| L
工作流引擎代码示例
import {
Workflow,
NodeConnectionTypes,
type IDataObject,
type INodeType,
type INodeTypeData,
type INodeTypes,
type IVersionedNodeType,
type LoadedClass
} from 'n8n-workflow';
const aiAgentNode = {
sourcePath: '',
type: {
description: {
displayName: 'AI Agent',
name: '@n8n/n8n-nodes-langchain.agent',
icon: 'fa:robot',
iconColor: 'black',
group: ['transform'],
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8],
description: 'Generates an action plan and executes it. Can use external tools.',
defaults: {
name: 'AI Agent',
color: '#404040',
},
inputs: [
NodeConnectionTypes.Main,
NodeConnectionTypes.AiLanguageModel,
NodeConnectionTypes.AiTool,
NodeConnectionTypes.AiMemory,
],
outputs: [NodeConnectionTypes.Main],
properties: [],
},
},
};
export class NodeTypes implements INodeTypes {
nodeTypes: {
'@n8n/n8n-nodes-langchain.agent': aiAgentNode,
}
getByName(nodeType: string): INodeType | IVersionedNodeType {
return this.nodeTypes[nodeType]?.type;
}
getByNameAndVersion(nodeType: string, version?: number): INodeType {
if (this.nodeTypes[nodeType]?.type) {
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType]?.type, version);
}
return mock<INodeType>({
description: {
properties: [],
name: nodeType,
},
});
}
getKnownTypes(): IDataObject {
throw new Error('Method not implemented.');
}
}
new Workflow({
nodeTypes: new NodeTypes(),
nodes: [
{
parameters: {},
name: 'Start',
type: 'test.set',
typeVersion: 1,
id: 'uuid-1',
position: [240, 300],
},
{
parameters: {
options: {},
},
name: 'AgentNode',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'uuid-2',
position: [460, 300],
},
{
parameters: {
options: {},
},
name: 'Set1',
type: 'test.set',
typeVersion: 1,
id: 'uuid-3',
position: [680, 300],
},
],
connections: {
Start: {
main: [
[
{
node: 'AgentNode',
type: NodeConnectionTypes.Main,
index: 0,
},
],
],
},
AgentNode: {
main: [
[
{
node: 'Set1',
type: NodeConnectionTypes.Main,
index: 0,
},
],
],
},
},
active: false,
});
节点加载机制
graph TD
A[启动n8n] --> B[加载配置文件]
B --> C[根据配置文件中的nodes配置]
C --> D{是否有自定义nodes目录}
D -->|是| E[从自定义目录加载nodes]
D -->|否| F[从默认目录加载nodes]
E --> G[解析nodes的元数据]
F --> G
G --> H[将nodes注册到系统]
H --> I[节点初始化]
I --> J[节点可用供工作流使用]
在目录packages/nodes-base/nodes
中新增或修改节点
pnpm build:nodes
Zeebe:面向微服务编排的分布式工作流引擎
核心定位:Zeebe 是 Camunda 公司开发的云原生工作流引擎,专为微服务架构设计,基于 BPMN 2.0 标准实现复杂业务流程的编排与监控。
技术特性:
- 分布式架构:通过无中心化设计的 Broker 集群实现水平扩展,支持高并发场景(如每秒处理数万流程实例)。
- BPMN 2.0 标准化:提供可视化建模工具(如 Camunda Modeler),支持流程版本控制、审计跟踪和合规性要求(如 SOX)。
- 容错与监控:内置分布式事务、 leader 选举机制,结合 Operate 等工具可实时追踪流程实例状态。
Nest.js 集成 Zeebe
安装依赖
pnpm add @camunda8/sdk
创建Zeebe Module
目录结构如下
./src/shared/modules/zeebe
├── index.ts
├── zeebe-module-options.interface.ts
├── zeebe.decorator.ts
├── zeebe.module-definition.ts
├── zeebe.module.ts
└── zeebe.service.ts
zeebe-module-options.interface.ts
import { Camunda8ClientConfiguration } from '@camunda8/sdk/dist/lib';
import { IOAuthProvider } from '@camunda8/sdk/dist/oauth';
export type ZeebeModuleOptions = Camunda8ClientConfiguration & {
oAuthProvider?: IOAuthProvider;
};
zeebe.module-definition.ts
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { ZeebeModuleOptions } from './zeebe-module-options.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } =
new ConfigurableModuleBuilder<ZeebeModuleOptions>()
.setExtras(
{
isGlobal: true,
},
(definition, extras) => ({
...definition,
global: extras.isGlobal,
}),
)
.build();
zeebe.decorator.ts
import { Camunda8ClientConfiguration } from '@camunda8/sdk/dist/lib';
import { ICustomHeaders, IInputVariables, IOutputVariables, ZBWorkerConfig } from '@camunda8/sdk/dist/zeebe/types';
import { applyDecorators, Injectable } from '@nestjs/common';
import { DiscoveryService } from '@nestjs/core';
export const ZeebeWorker = DiscoveryService.createDecorator<{
clientConfig?: Camunda8ClientConfiguration;
workerConfig: Omit<ZBWorkerConfig<ICustomHeaders, IInputVariables, IOutputVariables>, 'taskHandler'>;
}>();
export const Flow = DiscoveryService.createDecorator<{
name: string;
clientConfig?: Camunda8ClientConfiguration;
}>();
export const ZeebeFlow = (name: string, clientConfig?: Camunda8ClientConfiguration) => {
return applyDecorators(
Injectable(),
Flow({
name,
clientConfig,
}),
);
};
zeebe.service.ts
import { Camunda8 } from '@camunda8/sdk';
import { ZBWorker } from '@camunda8/sdk/dist/zeebe';
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
import { ZeebeModuleOptions } from './zeebe-module-options.interface';
import { Flow, ZeebeWorker } from './zeebe.decorator';
import { MODULE_OPTIONS_TOKEN } from './zeebe.module-definition';
@Injectable()
export class ZeebeService extends Camunda8 implements OnModuleInit {
private readonly logger: Logger = new Logger(ZeebeService.name);
private readonly workers: Map<string, ZBWorker<any, any, any>> = new Map();
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
private readonly options: ZeebeModuleOptions,
private readonly discoveryService: DiscoveryService,
private readonly metadataScanner: MetadataScanner,
) {
if (!options) {
throw new Error('ZeebeModuleOptions is not defined');
}
super(options);
}
async onModuleInit() {
this.createWorkers();
}
createWorkers() {
const providers = this.discoveryService.getProviders();
for (const wrapper of providers) {
if (!wrapper.metatype?.prototype) {
continue;
}
const flowMetadata = this.discoveryService.getMetadataByDecorator(Flow, wrapper);
if (!!flowMetadata) {
const handlers = this.metadataScanner.getAllMethodNames(wrapper.metatype.prototype);
for (const handler of handlers) {
const metadata = this.discoveryService.getMetadataByDecorator(ZeebeWorker, wrapper, handler);
if (!metadata) {
continue;
}
const { clientConfig, workerConfig } = metadata;
const type = `${flowMetadata.name}-${workerConfig.taskType}`;
const worker = this.getZeebeGrpcApiClient(clientConfig).createWorker({
...workerConfig,
taskType: type,
taskHandler: wrapper.instance[handler].bind(wrapper.instance),
});
this.logger.debug(`Zeebe worker[${type}] has been bound to the ${wrapper.name}.${handler}`);
this.workers.set(workerConfig.taskType, worker);
}
}
}
}
}
zeebe.module.ts
import { DynamicModule, Module } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from './zeebe.module-definition';
import { ZeebeService } from './zeebe.service';
@Module({
imports: [DiscoveryModule],
providers: [ZeebeService],
exports: [ZeebeService],
})
export class ZeebeModule extends ConfigurableModuleClass {
static register(options: typeof OPTIONS_TYPE): DynamicModule {
return {
...super.register(options),
};
}
static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
return {
...super.registerAsync(options),
};
}
}
zeebe.config.ts
import { ZeebeModuleOptions } from '@/shared/modules/zeebe';
import { registerAs } from '@nestjs/config';
export default registerAs<ZeebeModuleOptions>('zeebe', () => ({
zeebeGrpcSettings: {
ZEEBE_INSECURE_CONNECTION: Boolean(process.env.ZEEBE_INSECURE_CONNECTION),
},
ZEEBE_ADDRESS: process.env.ZEEBE_ADDRESS,
ZEEBE_CLIENT_ID: process.env.ZEEBE_CLIENT_ID,
ZEEBE_CLIENT_SECRET: process.env.ZEEBE_CLIENT_SECRET,
CAMUNDA_OAUTH_URL: process.env.CAMUNDA_OAUTH_URL,
CAMUNDA_TASKLIST_BASE_URL: process.env.CAMUNDA_TASKLIST_BASE_URL,
CAMUNDA_OPERATE_BASE_URL: process.env.CAMUNDA_OPERATE_BASE_URL,
CAMUNDA_OPTIMIZE_BASE_URL: process.env.CAMUNDA_OPTIMIZE_BASE_URL,
CAMUNDA_MODELER_BASE_URL: process.env.CAMUNDA_MODELER_BASE_URL,
}));
导入ZeebeModule
ZeebeModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => config.get("zeebe"),
})
定义流程
import { BaseComponent } from "@/common/components/base.component";
import { ZeebeFlow, ZeebeWorker } from "@/shared/modules/zeebe";
import { Logger } from "@nestjs/common";
@ZeebeFlow('test')
export class TestFlow extends BaseComponent {
private readonly logger = new Logger(TestFlow .name)
@ZeebeWorker({
workerConfig: {
taskType: 'start'
}
})
async start() {
this.logger.log('流程开始');
}
@ZeebeWorker({
workerConfig: {
taskType: 'terminate'
}
})
async terminate() {
this.logger.log('流程终止');
}
@ZeebeWorker({
workerConfig: {
taskType: 'end'
}
})
async end() {
this.logger.log('流程结束');
}
}
使用流程设计器创建BPMN
文件并关联事件
使用Zeebe
客户端
import { ZeebeService } from "@/shared/modules/zeebe";
await this.zeebeService.getZeebeGrpcApiClient().topology();
以下是@camunda8/sdk
中各客户端的作用解析:
-
Camunda REST API Client (
restClient
)- 作用:提供对Camunda Platform 8 REST API的完整访问
- 核心功能:
- 流程定义管理(部署/查询/删除)
- 流程实例控制(启动/取消/查询)
- 用户任务操作(获取/完成/查询)
- 历史数据查询(已完成实例/变量)
-
Zeebe gRPC API Client (
zeebe
)- 作用:直接操作Zeebe工作流引擎的核心客户端
- 核心功能:
- 部署BPMN流程(
deployResource
) - 创建/取消流程实例(
createProcessInstance
/cancelProcessInstance
) - 发布消息(
publishMessage
) - 修改流程变量(
setVariables
) - 任务处理(
activateJobs
/completeJob
)
- 部署BPMN流程(
-
Operate API Client (
operate
)- 作用:访问Operate监控服务的数据接口
- 核心功能:
- 查询运行中/已完成的流程实例
- 获取实例历史轨迹(Incident历史)
- 监控工作流健康状态
- 获取流程定义变更历史
-
Optimize API Client (
optimize
)- 作用:连接流程优化分析服务
- 核心功能:
- 获取流程性能报告(周期时间/瓶颈分析)
- 查询变更影响分析数据
- 获取合规性报告
- 导出分析数据集
-
Tasklist API Client (
tasklist
)- 作用:操作用户任务管理服务
- 核心功能:
- 获取用户任务列表(按用户/组过滤)
- 声明/完成用户任务
- 查询任务表单数据
- 管理任务注释和附件
-
Modeler API Client (
modeler
)- 作用:访问流程模型管理服务
- 核心功能:
- 上传/下载BPMN/DMN模型
- 获取模型版本历史
- 验证流程定义
- 模型比较功能
-
Admin API Client (
admin
)- 作用:系统管理接口
- 核心功能:
- 用户/组管理(CRUD操作)
- 权限控制(OAuth2客户端管理)
- 集群状态监控
- 审计日志查询
- 系统配置管理
对比矩阵
特性 | Inngest | n8n | Zeebe |
---|---|---|---|
定义方式 | TypeScript代码 | JSON可视化 | BPMN XML |
学习曲线 | ★☆☆(需编程基础) | ★★☆(低代码友好) | ★★★(BPMN标准) |
扩展性 | ★★★★(函数式编程) | ★★★☆(自定义节点) | ★★★★★(企业级插件) |
社区支持 | ★★☆(新兴社区) | ★★★★(活跃论坛) | ★★★☆(Camunda生态) |
典型场景 | 快速迭代的SaaS | 中小企业自动化 | 复杂企业流程 |
总结
-
初创团队/SaaS产品:选
Inngest
- 优点:TypeScript全栈开发、事件驱动架构、按需付费、AI友好
- 确定:默认使用SQLite进行数据持久化,对PostgreSQL数据库的支持目前是实验性的
-
中小型企业:选
n8n
- 优点:零代码可视化、丰富预置节点、本地部署友好、AI友好
- 缺点:允许一对一私有化部署但不允许基于n8n源码提供商业化SaaS服务
-
中大型企业/金融领域:选
Zeebe
- 优点:BPMN标准合规、分布式高可用、审计追踪、支持复杂流程
- 缺点:架构复杂,存在一定的学习曲线,需要投入更多时间和精力去掌握其架构、工作原理以及相关配置