feat(orchestration, workflows-sdk): Add events management and implementation to manage async workflows (#5951)

* feat(orchestration, workflows-sdk): Add events management and implementation to manage async workflows

* Create fresh-boxes-scream.md

* cleanup

* fix: resolveValue input ref

* resolve value recursive

* chore: resolve result value only for new api

* chore: save checkpoint before scheduling

* features

* fix: beginTransaction checking existing transaction

---------

Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2024-01-05 14:40:58 +01:00
committed by GitHub
parent 99a4f94db5
commit bf63c4e6a3
18 changed files with 1449 additions and 285 deletions

View File

@@ -2,17 +2,22 @@ import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
import { asValue } from "awilix"
import { DistributedTransaction } from "../transaction"
import {
DistributedTransaction,
DistributedTransactionEvents,
} from "../transaction"
import { WorkflowDefinition, WorkflowManager } from "./workflow-manager"
export class GlobalWorkflow extends WorkflowManager {
protected static workflows: Map<string, WorkflowDefinition> = new Map()
protected container: MedusaContainer
protected context: Context
protected subscribe: DistributedTransactionEvents
constructor(
modulesLoaded?: LoadedModule[] | MedusaContainer,
context?: Context
context?: Context,
subscribe?: DistributedTransactionEvents
) {
super()
@@ -35,6 +40,7 @@ export class GlobalWorkflow extends WorkflowManager {
this.container = container
this.context = context ?? {}
this.subscribe = subscribe ?? {}
}
async run(workflowId: string, uniqueTransactionId: string, input?: unknown) {
@@ -52,6 +58,18 @@ export class GlobalWorkflow extends WorkflowManager {
input
)
if (this.subscribe.onStepBegin) {
transaction.once("stepBegin", this.subscribe.onStepBegin)
}
if (this.subscribe.onStepSuccess) {
transaction.once("stepSuccess", this.subscribe.onStepSuccess)
}
if (this.subscribe.onStepFailure) {
transaction.once("stepFailure", this.subscribe.onStepFailure)
}
await orchestrator.resume(transaction)
return transaction
@@ -67,6 +85,21 @@ export class GlobalWorkflow extends WorkflowManager {
}
const workflow = WorkflowManager.workflows.get(workflowId)!
const orchestrator = workflow.orchestrator
orchestrator.once("resume", (transaction) => {
if (this.subscribe.onStepBegin) {
transaction.once("stepBegin", this.subscribe.onStepBegin)
}
if (this.subscribe.onStepSuccess) {
transaction.once("stepSuccess", this.subscribe.onStepSuccess)
}
if (this.subscribe.onStepFailure) {
transaction.once("stepFailure", this.subscribe.onStepFailure)
}
})
return await workflow.orchestrator.registerStepSuccess(
idempotencyKey,
workflow.handler(this.container, this.context),
@@ -85,6 +118,21 @@ export class GlobalWorkflow extends WorkflowManager {
}
const workflow = WorkflowManager.workflows.get(workflowId)!
const orchestrator = workflow.orchestrator
orchestrator.once("resume", (transaction) => {
if (this.subscribe.onStepBegin) {
transaction.once("stepBegin", this.subscribe.onStepBegin)
}
if (this.subscribe.onStepSuccess) {
transaction.once("stepSuccess", this.subscribe.onStepSuccess)
}
if (this.subscribe.onStepFailure) {
transaction.once("stepFailure", this.subscribe.onStepFailure)
}
})
return await workflow.orchestrator.registerStepFailure(
idempotencyKey,
error,

View File

@@ -3,6 +3,8 @@ import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
import { asValue } from "awilix"
import {
DistributedTransaction,
DistributedTransactionEvent,
DistributedTransactionEvents,
TransactionOrchestrator,
TransactionStepsDefinition,
} from "../transaction"
@@ -79,7 +81,174 @@ export class LocalWorkflow {
return this.workflow.flow_
}
async run(uniqueTransactionId: string, input?: unknown, context?: Context) {
private registerEventCallbacks({
orchestrator,
transaction,
subscribe,
idempotencyKey,
}: {
orchestrator: TransactionOrchestrator
transaction?: DistributedTransaction
subscribe?: DistributedTransactionEvents
idempotencyKey?: string
}) {
const modelId = orchestrator.id
let transactionId
if (transaction) {
transactionId = transaction!.transactionId
} else if (idempotencyKey) {
const [, trxId] = idempotencyKey!.split(":")
transactionId = trxId
}
const eventWrapperMap = new Map()
for (const [key, handler] of Object.entries(subscribe ?? {})) {
eventWrapperMap.set(key, (args) => {
const { transaction } = args
if (
transaction.transactionId !== transactionId ||
transaction.modelId !== modelId
) {
return
}
handler(args)
})
}
if (subscribe?.onBegin) {
orchestrator.on(
DistributedTransactionEvent.BEGIN,
eventWrapperMap.get("onBegin")
)
}
if (subscribe?.onResume) {
orchestrator.on(
DistributedTransactionEvent.RESUME,
eventWrapperMap.get("onResume")
)
}
if (subscribe?.onCompensateBegin) {
orchestrator.on(
DistributedTransactionEvent.COMPENSATE_BEGIN,
eventWrapperMap.get("onCompensateBegin")
)
}
if (subscribe?.onTimeout) {
orchestrator.on(
DistributedTransactionEvent.TIMEOUT,
eventWrapperMap.get("onTimeout")
)
}
if (subscribe?.onFinish) {
orchestrator.on(
DistributedTransactionEvent.FINISH,
eventWrapperMap.get("onFinish")
)
}
const resumeWrapper = ({ transaction }) => {
if (
transaction.modelId !== modelId ||
transaction.transactionId !== transactionId
) {
return
}
if (subscribe?.onStepBegin) {
transaction.on(
DistributedTransactionEvent.STEP_BEGIN,
eventWrapperMap.get("onStepBegin")
)
}
if (subscribe?.onStepSuccess) {
transaction.on(
DistributedTransactionEvent.STEP_SUCCESS,
eventWrapperMap.get("onStepSuccess")
)
}
if (subscribe?.onStepFailure) {
transaction.on(
DistributedTransactionEvent.STEP_FAILURE,
eventWrapperMap.get("onStepFailure")
)
}
if (subscribe?.onCompensateStepSuccess) {
transaction.on(
DistributedTransactionEvent.COMPENSATE_STEP_SUCCESS,
eventWrapperMap.get("onCompensateStepSuccess")
)
}
if (subscribe?.onCompensateStepFailure) {
transaction.on(
DistributedTransactionEvent.COMPENSATE_STEP_FAILURE,
eventWrapperMap.get("onCompensateStepFailure")
)
}
}
if (transaction) {
resumeWrapper({ transaction })
} else {
orchestrator.once("resume", resumeWrapper)
}
const cleanUp = () => {
subscribe?.onFinish &&
orchestrator.removeListener(
DistributedTransactionEvent.FINISH,
eventWrapperMap.get("onFinish")
)
subscribe?.onResume &&
orchestrator.removeListener(
DistributedTransactionEvent.RESUME,
eventWrapperMap.get("onResume")
)
subscribe?.onBegin &&
orchestrator.removeListener(
DistributedTransactionEvent.BEGIN,
eventWrapperMap.get("onBegin")
)
subscribe?.onCompensateBegin &&
orchestrator.removeListener(
DistributedTransactionEvent.COMPENSATE_BEGIN,
eventWrapperMap.get("onCompensateBegin")
)
subscribe?.onTimeout &&
orchestrator.removeListener(
DistributedTransactionEvent.TIMEOUT,
eventWrapperMap.get("onTimeout")
)
orchestrator.removeListener(
DistributedTransactionEvent.RESUME,
resumeWrapper
)
eventWrapperMap.clear()
}
return {
cleanUpEventListeners: cleanUp,
}
}
async run(
uniqueTransactionId: string,
input?: unknown,
context?: Context,
subscribe?: DistributedTransactionEvents
) {
if (this.flow.hasChanges) {
this.commit()
}
@@ -92,36 +261,104 @@ export class LocalWorkflow {
input
)
const { cleanUpEventListeners } = this.registerEventCallbacks({
orchestrator,
transaction,
subscribe,
})
await orchestrator.resume(transaction)
cleanUpEventListeners()
return transaction
}
async getRunningTransaction(uniqueTransactionId: string, context?: Context) {
const { handler, orchestrator } = this.workflow
const transaction = await orchestrator.retrieveExistingTransaction(
uniqueTransactionId,
handler(this.container, context)
)
return transaction
}
async cancel(
uniqueTransactionId: string,
context?: Context,
subscribe?: DistributedTransactionEvents
) {
const { orchestrator } = this.workflow
const transaction = await this.getRunningTransaction(
uniqueTransactionId,
context
)
const { cleanUpEventListeners } = this.registerEventCallbacks({
orchestrator,
transaction,
subscribe,
})
await orchestrator.cancelTransaction(transaction)
cleanUpEventListeners()
return transaction
}
async registerStepSuccess(
idempotencyKey: string,
response?: unknown,
context?: Context
context?: Context,
subscribe?: DistributedTransactionEvents
): Promise<DistributedTransaction> {
const { handler, orchestrator } = this.workflow
return await orchestrator.registerStepSuccess(
const { cleanUpEventListeners } = this.registerEventCallbacks({
orchestrator,
idempotencyKey,
subscribe,
})
const transaction = await orchestrator.registerStepSuccess(
idempotencyKey,
handler(this.container, context),
undefined,
response
)
cleanUpEventListeners()
return transaction
}
async registerStepFailure(
idempotencyKey: string,
error?: Error | any,
context?: Context
context?: Context,
subscribe?: DistributedTransactionEvents
): Promise<DistributedTransaction> {
const { handler, orchestrator } = this.workflow
return await orchestrator.registerStepFailure(
const { cleanUpEventListeners } = this.registerEventCallbacks({
orchestrator,
idempotencyKey,
subscribe,
})
const transaction = await orchestrator.registerStepFailure(
idempotencyKey,
error,
handler(this.container, context)
)
cleanUpEventListeners()
return transaction
}
addAction(