omervk / gamgee

An embedded framework to lend you a helping durable execution hand on your journey
1 stars 0 forks source link
Image generated by Copilot; Prompt: A hobbit looking up at you, offering his hand to help you, in a simple cartoon in the 1990's Cartoon Network style

Gamgee

An embedded framework to lend you a helping (durable execution) hand on your journey.

Build Status

TL;DR

Take a MermaidJS State Diagram, implement what each state executes and the engine durably executes it.

So write this:

---
title: ConditionsWorkflow
---

stateDiagram-v2
    state choice <<choice>>

    direction LR
    [*] --> decide
    decide --> choice
    choice --> left: chooseLeft
    choice --> right: chooseRight
    left --> [*]
    right --> [*]

Only implement this:

import { ConditionsWorkflowBase } from './conditions.generated'

export type DecidePayload = Record<string, never>
export type LeftPayload = Record<string, never>
export type RightPayload = Record<string, never>

export class ConditionsWorkflow extends ConditionsWorkflowBase {
    constructor() {
        super()
    }

    decide(payload: DecidePayload) {
        if (Math.random() < 0.5) {
            return Promise.resolve(this.choice.chooseLeft({}))
        } else {
            return Promise.resolve(this.choice.chooseRight({}))
        }
    }

    left(payload: LeftPayload): Promise<void> {
        console.log('We chose left')
        return Promise.resolve()
    }

    right(payload: RightPayload): Promise<void> {
        console.log('We chose right')
        return Promise.resolve()
    }
}

Then invoke an instance of it:

const workflow = new ConditionsWorkflow()
await workflow.submit({ /* initial payload */ }, stateStore)

const worker = new WorkflowWorker()
const result = await worker.executeWaitingWorkflow(
    stateStore,
    { workflowType: workflow.workflowType },    // what to execute
    FetchStrategy.Random,
    1000,                                       // timeout
)

Philosophy

This is being built to be lightweight, testable, localized/embedded and as type safe as I can make it, with a visual and single source of truth for how the workflow is built.

Workers should expect an influx of requests, and it’s preferable they be persistent and durable (can get an item, work on it, get the next item, etc.). Steps/tasks should be a single(?) idempotent IO call. The classic use case is when you have a streaming-like business flow, but a streaming framework like Kafka Streams is overkill.

WARNING: DO NOT USE!

This is very much a work in progress. Not even published to npm.

How To

// TODO: Build

TL;DR Usage

npm install --save-dev @gamgee/design @gamgee/test
npm install --save @gamgee/run

In your package.json:

"scripts": {
  "generate": "mm2ws **/*.mermaid",
}

Run:

npm run generate

Fill in the type-checked blanks.

Features

  1. Durable execution of workflows.
  2. Workflow is resumable.
  3. Multiple steps.
  4. Decision steps (do I execute X or Y next?)
  5. OpenTelemetry support (each step is a span)
  6. DAG only (detect cycles)

Examples

See the examples directory for a list of examples ordered by increasing complexity.