bullmq icon indicating copy to clipboard operation
bullmq copied to clipboard

Improve interface for defining Flows

Open jomhyre opened this issue 4 months ago • 3 comments

I have an app with a multistep workflow, for which I find the FlowProducer feature to be perfect for sequencing each step, and keeping everything robust when dealing with flaky APIs.

However, I find the interface for creating these flows to be awkward to both write and read, since they are essentially declared backwards:

await flowProducer.add({
    name: 'eat-veggies',
    children: [
        {
            name: 'roast-veggies',
            children: [
                {
                    name: 'chop-veggies',
                }
            ]
        }
    ]
})

I would prefer if I was able to define these flows as a simple sequence of steps, i.e.:

await flowProducer.add([
        {
            name: 'chop-veggies',
        },
        {
            name: 'roast-veggies',
        },
        {
            name: 'eat-veggies',
        },
    ]
)

or possibly even with a fluent interface/builder pattern, e.g.

await flowProducer.add(
    makeFlowOrSomething()
        .addStep("chop-veggies")
        .addStep("roast-veggies")
        .addStep("eat-veggies")
)

I've currently using my own hand-rolled helper function to do this

interface FlowNode {
  name: string;
  queueName: string;
  data: any;
  children?: FlowNode[];
}

function buildFlow(
    firstStep: FlowNode,
    ...remainingSteps: FlowNode[]
): FlowJob {
    let root: FlowNode = firstStep;

    for (const step of remainingSteps) {
        root = { ...step, children: [...(step.children ?? []), root] };
    }

    return root;
}

But I think it would be really nice if this were part of the library. Is there any reason not to take this approach?

It doesn't have to only apply to purely sequential flows as we could still have sibling nodes which run in parallel, e.g.:

await flows.add([
    {
        name: 'chop-veggies'
    },
    [
        {
            name: 'roast-veggies'
        },
        {
            name: 'set-table'
        }
    ],
    {
        name: 'eat-veggies'
    }
])

Excuse me if we already have something like this, I looked around and couldn't find it anywhere.

jomhyre avatar Aug 01 '25 16:08 jomhyre

@manast If this is needed, can I pick this ? What direction should we go in for implementing a new builder function.

Amitrochates avatar Aug 13 '25 06:08 Amitrochates

This is one of the points we have to emphasize when training new devs on flows. Once they understand that they are executed "inside-out", it's not too bad, but I think the semantics could definitely be improved.

rosslavery avatar Aug 14 '25 00:08 rosslavery

I am not sure how this could be improved honestly, unless you sacrifice functionality. Would you define children first and then the parent of those children? In my mind that would be even more awkward. But if you are just implementing a sub-case of flows, where you only have a sequence of jobs, then the current API is not optimal for that particular case, but it is easy to create a 2 lines helper that does it for you.

manast avatar Aug 14 '25 16:08 manast