萨加
为TypeScript执行轻量级,型号安全的saga模式,并自动回滚支持.
特征
- 类型安全: 完整的类型脚本支持,并带有智能类型推论
- 自动回滚: 失败的步骤按倒序自动触发补偿
- 零依赖关系:轻量级和生产准备
- 简单 API: 用于定义 sagas 的直观构建器模式
- 弹性: 任何同步操作的工程(数据库、API呼叫等)
安装
npm install saga-ts
快速启动
import { createSaga } from 'saga-ts';
const result = await createSaga()
.step(
'createUser',
{ email: '[email protected]', name: 'John' },
async (input, ctx) => {
const user = await db.users.create(input);
return { userId: user.id, email: input.email };
},
async (output) => {
// Rollback: delete the user if something fails later
await db.users.delete(output.userId);
}
)
.step(
'sendEmail',
{ template: 'welcome' },
async (input, ctx) => {
// Access previous step results with full type safety
const { email } = ctx.results.createUser;
const emailId = await emailService.send({
to: email,
template: input.template,
});
return { emailId, sentAt: new Date() };
},
async (output) => {
await emailService.cancel(output.emailId);
}
)
.run();
if (result.status === 'success') {
console.log('User created:', result.results.createUser.userId);
console.log('Email sent:', result.results.sendEmail.emailId);
} else {
console.error('Failed at step:', result.failedAt);
console.error('Error:', result.error);
}
API 参考
createSaga()
创建新的 saga 构建实例 .
.step(name, input, execute, rollback?)
加一相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相.
- 名称: 该步骤的独特标识符(用于获取结果)
- 投入: 步骤输入数据
- 执行: 执行步骤动作的自动同步函数
- 接收
input财务报告和审定财务报表ctx(包含以往步骤的结果) - 必须返回步骤的输出
- 接收
- 倒滚 (可选) : 取消步骤动作的自动同步函数
- 接收步骤的输出
- 如果后一步失败, 则自动调用
.run()
执行"萨加",并随结果返回承诺.
成功结果:
{
status: 'success',
data: TLastOutput, // Output of the last step
results: TResults // All step results by name
}
失败结果 :
{
status: 'failed',
error: Error, // The error that occurred
failedAt: string // Name of the step that failed
}
实例
用回滚处理付款
const result = await createSaga()
.step(
'validateCard',
{ cardToken: 'tok_visa' },
async (input) => {
const card = await stripe.tokens.retrieve(input.cardToken);
return { cardId: card.id, last4: card.card.last4 };
}
)
.step(
'calculateTotal',
{ items: [{ id: '1', price: 100 }, { id: '2', price: 50 }] },
async (input) => {
const total = input.items.reduce((sum, item) => sum + item.price, 0);
return { total, items: input.items };
}
)
.step(
'createCharge',
{ currency: 'usd' },
async (input, ctx) => {
const charge = await stripe.charges.create({
amount: ctx.results.calculateTotal.total,
currency: input.currency,
source: ctx.results.validateCard.cardId,
});
return { chargeId: charge.id };
},
async (output) => {
// Rollback: refund the charge
await stripe.refunds.create({ charge: output.chargeId });
}
)
.step(
'sendReceipt',
{ recipientEmail: '[email protected]' },
async (input, ctx) => {
await emailService.send({
to: input.recipientEmail,
template: 'receipt',
data: {
chargeId: ctx.results.createCharge.chargeId,
amount: ctx.results.calculateTotal.total,
},
});
return { sent: true };
}
)
.run();
// If sendReceipt fails, the charge will be automatically refunded
复杂的用户登入
const result = await createSaga()
.step(
'createAccount',
{ email: '[email protected]', password: 'secure123' },
async (input) => {
const account = await db.accounts.create(input);
return { accountId: account.id };
},
async (output) => {
await db.accounts.delete(output.accountId);
}
)
.step(
'createProfile',
{ name: 'John Doe', avatar: 'default.png' },
async (input, ctx) => {
const profile = await db.profiles.create({
...input,
accountId: ctx.results.createAccount.accountId,
});
return { profileId: profile.id };
},
async (output) => {
await db.profiles.delete(output.profileId);
}
)
.step(
'assignRole',
{ role: 'user' },
async (input, ctx) => {
await db.roles.assign({
accountId: ctx.results.createAccount.accountId,
role: input.role,
});
return { role: input.role };
},
async (output, ctx) => {
await db.roles.revoke({
accountId: ctx.results.createAccount.accountId,
role: output.role,
});
}
)
.step(
'sendWelcomeEmail',
{ template: 'onboarding' },
async (input, ctx) => {
const emailId = await emailService.send({
to: ctx.results.createAccount.email,
template: input.template,
});
return { emailId };
}
)
.run();
如何运作
- 步骤执行:步骤按定义顺序顺序执行
- 背景宣传:每个步骤从以往的所有步骤得到结果,直至
ctx.results - 类型安全: TypeScript 自动推断并验证步骤输入和输出的类型
- 处理错误: 如果任何步骤出现出错, 立即停止执行
- 自动回滚: 所有成功执行步骤的补偿功能按倒序调用
- 后退复原力: 如果回滚失败, 错误会被记录, 但其它回滚继续
类型安全
saga-ts提供完整的类型推断:
const result = await createSaga()
.step('step1', { value: 10 }, async (input) => {
return { doubled: input.value * 2 };
})
.step('step2', { multiplier: 3 }, async (input, ctx) => {
// TypeScript knows ctx.results.step1.doubled is a number
const result = ctx.results.step1.doubled * input.multiplier;
return { final: result };
})
.run();
if (result.status === 'success') {
// TypeScript knows the exact shape of results
const doubled: number = result.results.step1.doubled;
const final: number = result.results.step2.final;
}
处理最佳做法出错
- 总是提供回滚函数 用于修改状态的步骤
- 保持回滚自制 - 他们可能多次被调用
- 日志倒滚失败 - 图书馆记录下来,但继续
- 测试回滚逻辑 确保补偿工作正确
已知的限制
这个库是为了简单和类型安全而设计的. 对于更为复杂的组织需要,请考虑这些局限性:
仅顺序执行
步骤一次执行一个顺序 。 如果你有独立的步骤 可以平行运行, 他们仍然会等待对方完成.
// These steps run sequentially even though they're independent
.step('fetchUserProfile', {}, async () => { /* ... */ })
.step('fetchUserPreferences', {}, async () => { /* ... */ })
工作周边: 同时使用独立 sagas 运行 Promise.all()。 。 。 .
无内建重试逻辑
如果一个步骤因瞬态问题(网络超时,速率限制)而失败,则整个saga会失败并回滚. 指数回放没有自动重试.
工作周边: 执行步骤函数中的重试逻辑,或在重试处理器中将 saga 执行包起来.
有限回滚可观察性
当回滚函数失败时,错误会被记录到 console.error 但不包含在saga结果中. 你不会知道赔偿是不是部分失败的.
工作周边: 如果您需要详细的报酬审计记录, 请在回滚函数内执行自己的错误跟踪 .
只有记忆
所有步骤结果在执行期间被存储在内存中. 对于能回放出大有效载荷的长期活塞或步骤,这可能造成内存问题. 无相相相相相相相相相相相相相相相相相.
影响:如果您的进程崩溃了中相,就无法恢复. 对于关键的工作流程,考虑工作流程引擎如Temperor或Porder.
无条件逻辑
除非前一步失败,否则每一步都会运行. 你不能根据条件跳过台阶.
// Can't do: "if user is premium, skip payment step"
工作周边: 使用条件性逻辑内步函数来提前返回,或被分拆为单独的 sagas.
没有超时或取消
步骤可以无限地运行。 没有内置的超时机制或中止Signal支持.
工作周边: 使用 Promise.race() 或终止控制器.
何时使用 Sagas
萨加斯是理想的:
- 需要原子化的多步骤业务流程
- 服务间分配交易
- 具有补偿逻辑的复杂工作流程
- 需要保持失败一致性的业务
许可证
麻省理工学院
捐款
欢迎捐款! 请随意提交拉力请求.