flow-driven-domain

FDD App Generator

Generate a complete, runnable Spring Boot application on top of the flow-driven-domain (FDD) library. FDD turns a domain into a process-centric one: actions/states/transitions are declared in a workflow JSON, a FlowEngine drives the process, and flow history is persisted with the aggregate.

Your job: take the user’s process description, design the state machine, confirm it, then emit a full project that compiles and runs. The framework stack is fixed: io.github.progmodek:flow:1.1.0, Spring Boot 4.1.0, Java 25, Postgres (jsonb), Flyway. The build tooling, however, adapts to where the skill is invoked — a new module inside the caller’s existing Gradle/Maven build, or a standalone project — so read references/build-files.md and pick the layout in Step 4 before writing any build files. Reactive is out of scope — always generate the imperative (blocking) stack.

Read the reference files as you go — do not generate from memory. In particular, copy the exact import statements from the “Exact imports” section at the top of references/fdd-api.md — the framework packages are counter-intuitive (FlowAction/FlowState/FlowType live in com.progmod.flow.domain.service.parser.definition, and ActionDelegate/SystemActionDelegate in com.progmod.flow.domain.service.delegate, not in domain.model / domain.delegate). Guessing these is the #1 cause of a generated app failing to compile.

Workflow

Step 1 — Understand the process (design, don’t interrogate)

The user gives a natural-language description. Design the flow yourself, then confirm — don’t walk them through a long questionnaire. From the description infer:

If the description is genuinely ambiguous on something that changes the machine (e.g. “should an unreviewed request auto-expire, and after how long?”), ask a short, targeted question — don’t ask about things you can reasonably default.

Step 2 — Choose the mode (ask this explicitly)

There are two usage models; the user must pick (see fdd-api.md §1). Ask which they want, briefly explaining the trade-off:

Recommend Flowable-direct when the description clearly has domain data + invariants; recommend BaseFlow when it’s a thin orchestration/approval-style flow. Let the user decide.

Step 3 — Confirm the design

Present a compact spec before writing any files: aggregate + fields, the state list (mark initial & terminal), the action list (mark USER/SYSTEM), and the transition table (action, from → to, branches/retries/timers). Keep it skimmable. Get a yes (or adjust), then generate.

Also confirm (or state your defaults and proceed): base package (default com.example.<domain>), app/root name, DB schema name, and the HTTP base path.

Step 4 — Pick the build layout (detect the context)

Before writing any build files, work out where this app is being generated, because it decides whether you emit a standalone project or wire a module into the caller’s existing build. Look at the target directory and its parents:

Auto-detect, then state what you found and are about to do (“this is a Gradle build — I’ll add <app> as a subproject”) so the user can override (e.g. force standalone, or pick Gradle over Maven). The counterpart to settings.gradle/pom.xml wiring, the exact build-file templates for all four layouts, and their per-layout verify commands live in references/build-files.md — read it now and follow the matching layout in the next step. Everything below the build files (Java, resources, migration, docker-compose, README) is identical across layouts.

Step 5 — Generate the project

Create the app under the chosen location (a module directory inside the build, or a standalone ./<app-name>/). Mirror the chosen example’s structure, wiring, and idioms exactly — only the domain specifics change. Generate every file:

  1. Build file(s) — per the layout picked in Step 4, following references/build-files.md: the module build.gradle or pom.xml, plus the root-build wiring (include/<module>) for a subproject/submodule, or the full standalone build (with Gradle wrapper only for standalone Gradle). Keep the exact framework/plugin/dependency versions.
  2. The *Application.java (@SpringBootApplication @EnableScheduling) and *Config.java (the FlowRepository + FlowEngine beans).
  3. domain/flow/ — the *Action, *State, *FlowType enums.
  4. domain/ — the aggregate + entities (Flowable-direct) or nothing extra (BaseFlow).
  5. domain/delegate/ — one @Component ActionDelegate per action. Bean name = class name with first letter lowercased, and it must equal the delegate string in the JSON.
  6. dto/ — request records (Jackson com.fasterxml.jackson.annotation.* namespace).
  7. infra/primary/ — the REST controller (one endpoint per USER action + create + GET) and an ErrorHandler. infra/secondary/one working EventsPublisher (a LoggingEventPublisher that reads the flow’s domain events and logs them). Always generate exactly one: it is the evidence that publishing domain events is plug-in — the engine fans out to every EventsPublisher bean after each action, so adding another sink (Kafka, an HTTP webhook, an outbox) is just another @Component. Keep the generated one a log sink (no Kafka/broker) so the app runs on Postgres alone, with a comment marking the body as the swap-point for a real destination. See fdd-api.md §8.
  8. resources/flow/<name>.json — the workflow JSON (validate against the workflow-json.md checklist).
  9. resources/application.yaml — datasource + Flyway (own schema) + flow.* props.
  10. resources/db/migration/V1__Initial_version.sql — schema + aggregate table (id column type matches the id type: uuid for UUID, varchar for BaseFlow) + the mandatory flow_task table.
  11. docker-compose.yml — copy from assets/docker-compose.yml (skip if the caller’s build already ships one at the repo root that exposes Postgres on 5432; reuse it instead).
  12. README.md — fill in assets/README.template.md (states, actions, curl examples for each USER endpoint, run instructions). For a module, note how to run it from the root build (./gradlew :<module>:bootRun or mvn -pl <module> spring-boot:run).

Step 6 — Verify it builds

Confirm the app is actually runnable before handing off. The command depends on the layout (see the “Verify” line for each layout in references/build-files.md):

# standalone Gradle:   cd <app-dir> && ./gradlew compileJava --console=plain -q
# Gradle subproject:   ./gradlew :<module>:compileJava --console=plain -q   # from the build root
# standalone Maven:    cd <app-dir> && mvn -q compile
# Maven submodule:     mvn -q -pl <module> -am compile                      # from the build root

Fix any compilation errors. The usual suspects, in order of frequency:

  1. Wrong import package (package com.progmod.flow... does not exist) — re-check against the “Exact imports” section of references/fdd-api.md. FlowAction/FlowState/FlowType...domain.service.parser.definition; ActionDelegate/SystemActionDelegate...domain.service.delegate. Do not add a dependency to “fix” a missing framework package.
  2. A delegate bean name not matching the JSON delegate string.
  3. A state/action referenced in the JSON but missing from the enum. The full build (./gradlew build / mvn package) also works but needs no DB; bootRun needs Postgres up.

Compile ≠ runs. A clean compile does NOT exercise Flyway, the datasource, or delegate wiring — e.g. a missing spring-boot-flyway module lets the app start but silently skips all migrations, so flow_task never gets created and the task consumer errors every second. When you can (Postgres available), do a real smoke test: docker compose up -d, boot the app, and confirm the startup log shows Migrating schema ... to version and no relation "flow_task" does not exist. If you can’t boot it, tell the user the app is compile-verified but not run-verified, and give them the smoke-test steps.

Then tell the user how to run it (docker compose up → the layout’s bootRun/spring-boot:run command from references/build-files.md) and give a copy-pasteable curl walkthrough that drives the process end to end (create → each USER action → GET to watch state evolve, noting where a SYSTEM action fires automatically).

Correctness rules (the things that break FDD apps)

These come from fdd-api.md §10 — check them in the generated output:

Notes