Skip to main content

Overview

The scheduling module provides tools for scheduling process steps based on their dependencies. It uses a directed acyclic graph (DAG) to model dependencies and topological sorting to produce valid execution schedules.

Key Components

Process

Represents a process with multiple steps and their dependency relationships.
package com.softwarearchetypes.graphs.scheduling;

record Process(
    Set<ProcessStep> steps,
    Graph<ProcessStep, DefaultEdge> dependencyGraph,
    Map<EdgeKey, DependencyType> edgeDependencyTypes
) {
    static Builder builder();
}
steps
Set<ProcessStep>
required
All process steps in the process
dependencyGraph
Graph<ProcessStep, DefaultEdge>
required
Directed acyclic graph of step dependencies (JGraphT DirectedAcyclicGraph)
edgeDependencyTypes
Map<EdgeKey, DependencyType>
required
Maps edges to their dependency types (FINISH_TO_START, START_TO_START, etc.)

Process.Builder

Fluent builder for constructing processes with dependencies.
static class Builder {
    Builder addStep(ProcessStep step);
    Builder addDependency(ProcessStep from, ProcessStep to);
    Builder addDependency(ProcessStep from, ProcessStep to, DependencyType type);
    Schedule build();
}

addStep()

Adds a process step to the process.
Builder addStep(ProcessStep step)
step
ProcessStep
required
Process step to add
return
Builder
Builder instance for fluent chaining

addDependency(ProcessStep, ProcessStep)

Adds a dependency between two steps (default FINISH_TO_START).
Builder addDependency(ProcessStep from, ProcessStep to)
from
ProcessStep
required
Prerequisite step that must complete first
to
ProcessStep
required
Dependent step that requires the prerequisite
return
Builder
Builder instance for fluent chaining
Semantics:
  • Creates a directed edge: from → to
  • “to” can only start after “from” completes (finish-to-start)
  • Automatically adds both steps to the graph if not already present

addDependency(ProcessStep, ProcessStep, DependencyType)

Adds a typed dependency between two steps.
Builder addDependency(ProcessStep from, ProcessStep to, DependencyType type)
from
ProcessStep
required
Prerequisite step
to
ProcessStep
required
Dependent step
type
DependencyType
required
Type of dependency relationship
return
Builder
Builder instance for fluent chaining
Dependency Types:
  • FINISH_TO_START: to starts after from finishes (most common)
  • START_TO_START: to starts when from starts (parallel start)
  • FINISH_TO_FINISH: to finishes when from finishes (synchronized end)
  • START_TO_FINISH: to finishes when from starts (rare)

build()

Builds the process and generates an execution schedule.
Schedule build()
return
Schedule
Schedule containing topologically sorted process steps
Algorithm:
  1. Creates Process record with steps and dependency graph
  2. Applies topological sort to dependency graph
  3. Returns Schedule with steps in valid execution order
Throws:
  • Cycle detection if dependencies form a cycle (enforced by DirectedAcyclicGraph)

Schedule

Represents an ordered sequence of process steps.
package com.softwarearchetypes.graphs.scheduling;

record Schedule(List<ProcessStep> steps) {
    ProcessStep first();
    ProcessStep last();
    int size();
    boolean isEmpty();
}
steps
List<ProcessStep>
required
Immutable list of process steps in execution order

Methods

first()
ProcessStep
Returns the first step in the schedule, or null if empty
last()
ProcessStep
Returns the last step in the schedule, or null if empty
size()
int
Returns the number of steps in the schedule
isEmpty()
boolean
Returns true if the schedule contains no steps

ProcessStep

Represents a single step in a process.
package com.softwarearchetypes.graphs.scheduling;

record ProcessStep(String name) {}

DependencyType

Enum defining types of dependencies between process steps.
package com.softwarearchetypes.graphs.scheduling;

enum DependencyType {
    FINISH_TO_START,   // Most common: B starts after A finishes
    START_TO_START,    // B starts when A starts (can run in parallel)
    FINISH_TO_FINISH,  // B finishes when A finishes
    START_TO_FINISH    // B finishes when A starts (rare)
}

Usage Examples

Simple Linear Process

ProcessStep design = new ProcessStep("Design");
ProcessStep implement = new ProcessStep("Implement");
ProcessStep test = new ProcessStep("Test");
ProcessStep deploy = new ProcessStep("Deploy");

Schedule schedule = Process.builder()
    .addStep(design)
    .addStep(implement)
    .addStep(test)
    .addStep(deploy)
    .addDependency(design, implement)      // Implement after design
    .addDependency(implement, test)        // Test after implement
    .addDependency(test, deploy)           // Deploy after test
    .build();

// schedule.steps() = [Design, Implement, Test, Deploy]

Parallel Steps with Dependencies

ProcessStep requirements = new ProcessStep("Requirements");
ProcessStep uiDesign = new ProcessStep("UI Design");
ProcessStep backendDesign = new ProcessStep("Backend Design");
ProcessStep implementation = new ProcessStep("Implementation");

Schedule schedule = Process.builder()
    .addDependency(requirements, uiDesign)
    .addDependency(requirements, backendDesign)  // UI and Backend can run in parallel
    .addDependency(uiDesign, implementation)
    .addDependency(backendDesign, implementation)  // Implementation needs both
    .build();

// Possible order: [Requirements, UI Design, Backend Design, Implementation]
// Or: [Requirements, Backend Design, UI Design, Implementation]

Manufacturing Process with Typed Dependencies

ProcessStep prepMaterial = new ProcessStep("Prepare Material");
ProcessStep heatTreatment = new ProcessStep("Heat Treatment");
ProcessStep machining = new ProcessStep("Machining");
ProcessStep qualityCheck = new ProcessStep("Quality Check");

Schedule schedule = Process.builder()
    .addDependency(prepMaterial, heatTreatment, DependencyType.FINISH_TO_START)
    .addDependency(heatTreatment, machining, DependencyType.FINISH_TO_START)
    .addDependency(machining, qualityCheck, DependencyType.START_TO_START)  // QC starts with machining
    .build();

Complex Process with Multiple Dependencies

ProcessStep A = new ProcessStep("A");
ProcessStep B = new ProcessStep("B");
ProcessStep C = new ProcessStep("C");
ProcessStep D = new ProcessStep("D");
ProcessStep E = new ProcessStep("E");

Schedule schedule = Process.builder()
    .addStep(A)
    .addDependency(A, B)
    .addDependency(A, C)      // B and C depend on A
    .addDependency(B, D)
    .addDependency(C, D)      // D depends on both B and C
    .addDependency(D, E)      // E is final step
    .build();

// Valid topological order: [A, B, C, D, E] or [A, C, B, D, E]

Iterating Over Schedule

Schedule schedule = buildSchedule();

System.out.println("Execution plan:");
for (int i = 0; i < schedule.size(); i++) {
    ProcessStep step = schedule.steps().get(i);
    System.out.println((i + 1) + ". " + step.name());
}

System.out.println("First step: " + schedule.first().name());
System.out.println("Last step: " + schedule.last().name());

Executing a Schedule

public class ProcessExecutor {
    public void execute(Schedule schedule) {
        for (ProcessStep step : schedule.steps()) {
            System.out.println("Executing: " + step.name());
            executeStep(step);
        }
    }
    
    private void executeStep(ProcessStep step) {
        // Implementation-specific execution logic
    }
}

Parallel Execution with Concurrency Module

The scheduling module integrates with the concurrency module for parallel execution:
import com.softwarearchetypes.graphs.scheduling.concurrency.Concurrency;

public class ParallelExecutor {
    private final Concurrency concurrency;
    private final Process process;
    
    public void executeInParallel() {
        // Concurrency module analyzes dependencies and executes
        // steps in parallel where dependencies allow
        concurrency.execute(process);
    }
}

Algorithm Details

Topological Sort

Algorithm: Kahn’s algorithm via JGraphT TopologicalOrderIterator Complexity:
  • Time: O(V + E) where V = steps, E = dependencies
  • Space: O(V)
Properties:
  • Produces a valid execution order respecting all dependencies
  • Multiple valid orderings may exist
  • Returns one valid ordering (not all possible orderings)
Guarantees:
  • If A depends on B, then B appears before A in schedule
  • All steps with no dependencies can be executed first
  • Parallel execution possible for steps with no dependency path between them

Cycle Detection

The underlying DirectedAcyclicGraph enforces acyclicity:
try {
    Schedule schedule = Process.builder()
        .addDependency(stepA, stepB)
        .addDependency(stepB, stepC)
        .addDependency(stepC, stepA)  // Cycle!
        .build();
} catch (IllegalArgumentException e) {
    // Cycle detected during graph construction
}

Integration with Concurrency

The scheduling.concurrency subpackage provides parallel execution:
package com.softwarearchetypes.graphs.scheduling.concurrency;

class Concurrency {
    // Analyzes schedule and executes steps in parallel
    // where dependencies allow
}

class ExecutionEnvironments {
    // Manages thread pools and execution contexts
}
See concurrency module documentation for details on parallel execution strategies.

Design Patterns

Builder Pattern

Process construction uses fluent builder for readability:
Process.builder()
    .addStep(step1)
    .addStep(step2)
    .addDependency(step1, step2)
    .build();

Immutable Records

Both Process and Schedule are immutable records ensuring thread safety and preventing accidental modification.

Graph Transformation

Process building transforms declarative dependencies into executable schedule:
Dependency Declarations → DAG → Topological Sort → Linear Schedule
  • ProcessStep: Individual step in a process
  • DependencyType: Type of dependency relationship
  • Schedule: Ordered execution plan
  • Concurrency: Parallel execution engine
  • ExecutionEnvironments: Thread pool management

Best Practices

Naming Steps

Use clear, action-oriented names:
// Good
new ProcessStep("Load Configuration")
new ProcessStep("Validate Inputs")
new ProcessStep("Execute Transform")

// Avoid
new ProcessStep("Step 1")
new ProcessStep("Do Stuff")

Dependency Granularity

Break down large steps into smaller ones for better parallelization:
// Better - allows parallel processing of A and B
Process.builder()
    .addDependency(init, processA)
    .addDependency(init, processB)
    .addDependency(processA, finalize)
    .addDependency(processB, finalize)
    .build();

// Worse - forces sequential execution
Process.builder()
    .addDependency(init, processAB)  // Combines A and B
    .addDependency(processAB, finalize)
    .build();

Error Handling

Plan for step failures:
public void executeWithRetry(Schedule schedule) {
    for (ProcessStep step : schedule.steps()) {
        try {
            executeStep(step);
        } catch (Exception e) {
            handleStepFailure(step, e);
            // Decide: retry, skip, or abort
        }
    }
}

References

  • Source: /workspace/source/graphs/src/main/java/com/softwarearchetypes/graphs/scheduling/
  • Process: Process.java:10-67
  • Schedule: Schedule.java:5-25
  • Concurrency: scheduling/concurrency/ package

Build docs developers (and LLMs) love