Write a simple workflow orchestration engine:
- Runs a DAG of tasks
- Edge represent "runs-after" relationships. No inputs or outputs, only order of execution constraints
- Tasks are
python
subprocesses - Only one task runs at a time
DAG nodes: start, 1, 2, end DAG edges: start -> 1, start -> 2, 2 -> end
Valid execution:
- start
- 1
- 2
- end
Alternative valid execution:
- start
- 2
- end
- 1
Represent the static-dynamic workflow gradient in as much precision as possible
Fully static workflows are those for which the entire DAG is known without running any tasks
Fully dynamic workflows are those for which only the first node is known but additional edges/nodes can be defined when a node runs
Requirements:
- Allow visualizing an accurate best possible as-of-this-moment DAG. Must accurately communicate all possible future execution paths (e.g. with a follow-up "wildcard" node)
- Support fully static workflows using some kind of "Registration" phase
- Support fully dynamic workflows by allowing a task to create nodes and edges arbitrarily while it runs
- Support in-between workflows by allowing the workflow to "Promise" (during Registration or during Runtime) a constraint. The Orchestrator must ensure a Promise is never broken (and terminate workflows that break Promises)
- All nodes/edges must be uniform. No DAG structure or execution pattern can be hard-coded in the Orchestrator. E.g. no hard-coded "branch" or "map-task" nodes/edges
Example Promises:
- No new nodes are created
- No new edges are created
- Only specific edges can be created
- Only specific nodes can be created
- No edges can be created starting at node A
- No edges can be created ending at node B
- etc.
You do not have to support any of the example promises
- Pre-determined workflow: DAG is always the same when the workflow finishes
- Randomly branching workflow: DAG is always either A or B when the workflow finishes (decided randomly at Runtime)
- Random map-task workflow: DAG is always A -> M* -> B where M repeats 0+ times (total number of repetitions decided randomly at Runtime)
- Use Python
asyncio
- Use SQLite to store state
- Use GraphViz to visualize DAGs
- Use JSON messages over stdio for task communication with workflow orchestrator -- do not need to design an more complicated RPC system / API
- Add strongly-typed task inputs/outputs
- Allow branching based on a task input
- Allow map-tasks to process a collection-type input and produce a collection-type output
- Allow Promises related to "subgraphs" or graph "components". E.g. Promise that DAG is A -> X -> B where X is of form (N -> M) OR X is of form (J -> K)
- Support running multiple tasks in parallel
- Allow pausing and resuming a workflow execution (disable running the next avilable task)
- Allow restarting a specific task (and all downstream tasks)
- Allow waiting for user input in the Orchestrator (without having a task process sleep/block)
- Support tasks that run Docker containers
- Support cosmetic changes to the DAG (and related Promises) e.g. node color, edge label etc.
- Support non-acyclic graphs