@abinnovision/nestjs-hatchet (英语)
雀巢JS整合 标签 - 一个分布式工作流程管弦引擎。 提供类型安全的装饰,服务和事件处理,以构建强大的工作流程.
安装
yarn add @abinnovision/nestjs-hatchet @hatchet-dev/typescript-sdk
@hatchet-dev/typescript-sdk 是一个对等依赖,所以您的应用程序控制 SDK
版本。 导入 SDK 等类型 Context 直接从它.
快速启动
1. 配置模块
import { HatchetModule } from "@abinnovision/nestjs-hatchet";
import { Module } from "@nestjs/common";
@Module({
imports: [
HatchetModule.forRoot({
config: {
token: process.env.HATCHET_CLIENT_TOKEN,
tls_config: { tls_strategy: "none" },
},
worker: { name: "my-worker" },
}),
],
})
export class AppModule {}
工人通过Nest的停机钩被拦住 等待工作
结束。 打电话 app.enableShutdownHooks() 所以这也发生在 SIGTERM编号 :SIGINT。 。 。 .
让Hatchet SDK自己处理这些信号 设定 handleKill: true 内
工人的选择.
2. 界定任务
import { Host, Task, TaskCtx, taskHost } from "@abinnovision/nestjs-hatchet";
import { z } from "zod";
@Host({ name: "process-data" })
export class ProcessDataTask extends taskHost(z.object({ data: z.string() })) {
@Task({})
public async task(ctx: TaskCtx<typeof this>) {
return { result: ctx.input.data.toUpperCase() };
}
}
3. 登记和举报
import { Client, HatchetModule, taskRef } from "@abinnovision/nestjs-hatchet";
import { Injectable, Module } from "@nestjs/common";
import { ProcessDataTask } from "./process-data.task";
@Injectable()
export class MyService {
constructor(private readonly client: Client) {}
async process(data: string) {
return this.client.run(taskRef(ProcessDataTask), { data });
}
}
@Module({
imports: [HatchetModule.forFeature(taskRef(ProcessDataTask))],
providers: [ProcessDataTask, MyService],
})
export class ProcessDataModule {}
forFeature() 只向工人登记宿主。 每个主机也必须列出
输入 providers 模块,否则工人无法解决这个问题
应用程序无法启动 .
核心概念
任务
采用单一可执行方法的独立工作单位.
@Host({ name: "cleanup" })
export class CleanupTask extends taskHost() {
@Task({})
public async task(ctx: TaskCtx<typeof this>) {
return { cleaned: true };
}
}
工作流量
具有任务依赖性的多步骤进程.
@Host({ name: "order-workflow" })
export class OrderWorkflow extends workflowHost(
z.object({ orderId: z.string() }),
) {
@WorkflowTask<typeof OrderWorkflow>({ parents: [] })
public async validate(ctx: WorkflowCtx<typeof this>) {
return { valid: true };
}
@WorkflowTask<typeof OrderWorkflow>({ parents: ["validate"] })
public async process(ctx: WorkflowCtx<typeof this>) {
const validation = await ctx.parent(this.validate);
return { processed: validation.valid };
}
}
活动
具有schema验证的类型安全事件定义.
// Define event
export const UserCreatedEvent = defineEvent(
"user:created",
z.object({ userId: z.string(), email: z.string().email() }),
);
// Workflow triggered by event
@Host({ name: "user-handler", onEvents: ["user:created"] })
export class UserHandlerWorkflow extends workflowHost() {
@WorkflowTask<typeof UserHandlerWorkflow>({ parents: [] })
public async handle(ctx: WorkflowCtx<typeof this>) {
if (UserCreatedEvent.isCtx(ctx)) {
return { userId: ctx.input.userId };
}
}
}
// Emit event
await client.emit(UserCreatedEvent, {
userId: "123",
email: "[email protected]",
});
高级使用
非锁定执行
const runRef = await ctx.run(taskRef(SomeTask), input, { wait: false });
// Continue without waiting...
const result = await runRef.output; // Await when needed
散装事件排放
await client.emit(OrderEvent, [
{ orderId: "1", total: 100 },
{ orderId: "2", total: 200 },
]);
任务截取器
任务截取器将每个任务执行包在工人身上,包括每个工作流程的任务,例如:
记录或追踪。 传过来 forRoot()编号 :forRootAsync() 并把他们列为供养者,因为他们
由应用程序而不是模块即时解决。 生产方
电话,看 客户截取器。 。 。 .
import {
BaseCtx,
HatchetModule,
TaskInterceptor,
} from "@abinnovision/nestjs-hatchet";
import { Injectable, Logger, Module } from "@nestjs/common";
@Injectable()
export class LoggingInterceptor extends TaskInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
public async intercept<T>(ctx: BaseCtx<any>, next: () => Promise<T>) {
this.logger.log(`Running ${ctx.fromSDK.taskName()}`);
return next();
}
}
@Module({
imports: [
HatchetModule.forRoot({
config: {},
worker: { name: "my-worker" },
taskInterceptors: [LoggingInterceptor],
}),
],
providers: [LoggingInterceptor],
})
export class AppModule {}
客户截取器
客户端截取器包装制片方呼叫 Client数字 : client.emit (和
已贬值 emitBulk和 client.run扩展 ClientInterceptor 执行
interceptEmit, (中文(简体) ), interceptRun或两者兼有。 以
clientInterceptors 选项 forRoot()编号 :forRootAsync() 并列出在 providers。 。 。 .
以下例子宣传了一种关联性标识。 客户截取器写到
additionalMetadata并有一个任务拦截器 工人从
运行中任务的元数据 .
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import {
BaseCtx,
ClientInterceptor,
EmitCtx,
EmitOptions,
HatchetModule,
TaskInterceptor,
RunCtx,
RunOptions,
} from "@abinnovision/nestjs-hatchet";
import { Injectable, Module } from "@nestjs/common";
export const correlation = new AsyncLocalStorage<{ id: string }>();
@Injectable()
export class CorrelationClientInterceptor extends ClientInterceptor {
public override async interceptEmit(
ctx: EmitCtx,
next: (options?: EmitOptions) => Promise<unknown>,
) {
const id = correlation.getStore()?.id;
if (!id) return next();
return next({
...ctx.options,
additionalMetadata: {
correlationId: id,
...ctx.options?.additionalMetadata,
},
});
}
public override async interceptRun(
ctx: RunCtx,
next: (options?: RunOptions) => Promise<unknown>,
) {
const id = correlation.getStore()?.id;
if (!id) return next();
return next({
...ctx.options,
additionalMetadata: {
correlationId: id,
...ctx.options?.additionalMetadata,
},
});
}
}
@Injectable()
export class CorrelationInterceptor extends TaskInterceptor {
public async intercept<T>(ctx: BaseCtx<any>, next: () => Promise<T>) {
const id = ctx.fromSDK.additionalMetadata()?.correlationId ?? randomUUID();
return correlation.run({ id }, next);
}
}
@Module({
imports: [
HatchetModule.forRootAsync({
useFactory: () => ({ config: {}, worker: { name: "my-worker" } }),
clientInterceptors: [CorrelationClientInterceptor],
taskInterceptors: [CorrelationInterceptor],
}),
],
providers: [CorrelationClientInterceptor, CorrelationInterceptor],
})
export class AppModule {}
行为:
- 每个拦截器每次呼叫一次 散装弹出(数列有效载荷)和
run与 数组输入每个计为一个调用 . - 截取器只改变选项。 有效载荷或输入不可改变.
next()保留当前选项.next(options)替换如下: 截取器和SDK呼叫,所以散开ctx.options以保持呼叫者值.next(undefined)行为就像next()。要放弃所有选项,请通过{}。 。 。 .wait和返回形状client.run不能被推翻.RunOptions不包括wait。 。 。 .- 与
wait: true(默认),interceptRun覆盖全部运行,包括 等待输出。 与wait: false它只包着花序. childKey没有任何影响Client.run。 。 。 .- 拦截器按阵列顺序运行,第一个是最外层. 拦截器可能
跳过
next()把电话传短路. - 客户端截取类必须列出于
providers失败 应用拖带. - 儿童运行始于
ctx.run继承父方的additionalMetadata打开 弹夹引擎>= v0.104.0所以他们不需要客户拦截器.
洞穴:
- 通话
client.emit或者说client.run在一个拦截器内部 重新进入链. 保护自己避免重犯. - 不支持请求范围拦截器提供者, 与任务相同 拦截器.
帮助方法
@Host({ name: "my-task" })
export class MyTask extends taskHost(schema) {
@Task({})
public async task(ctx: TaskCtx<typeof this>) {
return this.helper(ctx);
}
private async helper(ctx: HelperCtx<typeof this>) {
return { result: ctx.input.value };
}
}
API 参考
模块
| 方法 | 说明 |
|---|---|
HatchetModule.forRoot(config) | 以同步配置初始化 |
HatchetModule.forRootAsync(options) | 初始化为 async 配置厂 |
HatchetModule.forFeature(...refs) | 特征模块中的任务/工作流程注册 |
模块选项( extras to forRoot编号 :forRootAsync:
| 选项 | 说明 |
|---|---|
taskInterceptors | TaskInterceptor 类 环绕每个任务执行 |
interceptors | 折旧、使用 taskInterceptors |
clientInterceptors | ClientInterceptor 类包 emit 财务报告和审定财务报表 run |
装饰者
| 装饰 | 说明 |
|---|---|
@Host(opts) | 将类标为任务或工作流程主机 |
@Task(opts) | 将方法标为独立任务 |
@WorkflowTask(opts) | 将方法标为工作流程步骤 |
主机厂
| 工厂 | 说明 |
|---|---|
taskHost(schema?) | 创建任务控点类 |
workflowHost(schema?) | 创建 WorkflowHost 基础类 |
参考资料
| 函数 | 说明 |
|---|---|
taskRef(TaskClass) | 创建类型安全任务引用 |
workflowRef(WorkflowClass) | 创建类型安全的工作流程引用 |
客户端
| 方法 | 说明 |
|---|---|
client.run(ref, input, opts?) | 执行任务/工作流程 |
client.emit(event, payload | payloads) | 释放一个或许多事件 |
client.emitBulk(event, payloads) | 释放多起事件( 过期) – 使用 emit(中文(简体) ) |
呼吁 emit, (中文(简体) ), emitBulk 财务报告和审定财务报表 run 通过注册 客户端截取器。 。 。 .
上下文类型
| 类型 | 说明 |
|---|---|
TaskCtx<T> | 任务执行的背景 |
WorkflowCtx<T> | 工作流程任务的背景 |
HelperCtx<T> | 帮助方法的背景 |
上下文属性
ctx.input // Validated input data
ctx.fromSDK // Underlying Hatchet SDK context
ctx.run(ref, input, opts ?) // Invoke other tasks/workflows
ctx.parent(method) // Get parent task output (WorkflowCtx only)
许可证
阿帕奇 - 2.0