ez-flow – Typescript library for a workflow engine
Tags: typescript, programming, productivity, tooling
Published: 2024-02-25 14:42:44

This library was heavily inspired to [j-easy/easy-flows](https://github.com/j-easy/easy-flows) in the Java world.

## Definitions
A **Workflow** is a collection of **Work Units** must be run tu achieve a given purpose.

Each _Work_ unit implements a behaviour and interacts with outer environment and the other units by the means of a **Context** exchanged between them. You can place each unit in a flow applying some logic to control it.

That is you can place unit in a sequential flow, in a conditional one, in a parallel one and so on.

At the end of this "chain" we obtain the final result.

In this library are actually available the following flow constructs:

- **Sequential Flow**
- **Conditional Flow**
- **Iterative Flow**
- **Parallel Flow**

![Flows definitions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10gdh01zjligtse1hevd.png)

### How to apply a flow?

Suppose you have defined a work unit called **PrintMessageWork** that implements _Work_. It takes a _message_ and its _call_ method prints it:<br/>

```typescript
import {
  Work,
  WorkContext,
  WorkReport,
  DefaultWorkReport,
  WorkStatus,
} from '@rs-box/ez-flow';

export class PrintMessageWork implements Work {
  private message: string;

  constructor(message: string) {
    this.message = message;
  }

  getName() {
    return 'print message';
  }

  async call(workContext: WorkContext): Promise<WorkReport> {
    console.log(this.message);
    return new DefaultWorkReport(WorkStatus.COMPLETED, workContext);
  }
}
```

## Use case

To test the library, a use case scenario was built in which, given a list of Italian cities, I wish to perform certain operations on each city and produce results.

To achieve this, it was important to first define the available inputs and what the workflow wanted to obtain, in this case the cumulative data on the regions to which the cities belong.

## Installation

```bash
npm install @rs-box/ez-flow
```

## Workflows definition

Specifically, three workflows were built:
* **Main** workflow
* **Loop over cities** workflow
* **Operations** workflow

### Main

This is the main workflow, a *Sequential Flow* that performs two operations:
* Validate the context given as input (*ValidateContext* unit).
* Calling the *Loop over cities* workflow.

![Main workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xs16bedflfuqodx9n51p.png)

### Loop over cities

This is a *Repeat Flow*, it will run until the *IsNotLastCity* predicate returns a negative value

![Loop over cities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w8itum2kxdjbbi093xxh.png)

### Operations city

This is a *Parallel Flow* that executes the list of units passed as input, in the specific case of this test it will perform the following actions:
* Print the city name
* Update the cumulative region data.

![Operations city](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk4fba6w6fgp0m1115l5.png)

### Complete

This is the complete workflow resulting from the combination of those shown above.

![Complete workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cex0cirkq1hwlsrbzf9x.png)

## Result

Finally, the workflow described above is translated as follows:

```typescript
const workflow = SequentialFlow.Builder.newFlow()
  .withName('Main workflow')
  .addWork(new ValidateContextWork())
  .addWork(
    RepeatFlow.Builder.newFlow()
      .withName('Loop over cities')
      .withWork(
        ParallelFlow.Builder.newFlow()
          .withName('Operations city')
          .withWorks([new PrintCityWork(), new RegionCity()])
          .build(),
      )
      .until(new IsNotLastCityPredicate())
      .build(),
  )
  .build();
  ```

### Source code
You can find the full source code for this scenario here:
{% embed https://github.com/rstanziale/ez-flow-test %}