5. One API per file, deterministic whole-run generation, generated-file ownership
Context
A generation run reads a set of BPMN files and writes source files into a package. Three questions need a fixed answer: what one file turns into, what the output depends on, and which files in the target directory belong to the generator. The first question is not trivial, because the same process often exists in several files, one per location or environment, all declaring the same process id.
Decision
Every BPMN file yields one Process API, and one JSON document in the JSON export. Nothing is merged. The name of both is derived in one place, ProcessModel.apiName: the process id, led by the optional process extension property variantName. Files that share a process id are told apart by giving each a variantName. There is no setting for this in any entry point; declaring the property is the opt-in. A variant name has to start with a letter and may contain letters, digits, _ and -, because it becomes part of a type name and a file name.
Names must be distinct. Before anything is written, files that would be generated under the same name are rejected, and the error names the files. This covers a process id declared twice as well as two ids that only collide once normalised.
A run is whole. All matched files are processed together, because the shared definition files (record 6) are computed across them. A process marked non-executable gets no Process API and is skipped with a log line; the JSON export still emits it.
Output is a function of the inputs. Every model is normalised before generation: duplicates are dropped and every scope and registry is sorted, and the models are ordered by their API name. The same files produce byte-identical output on every machine and file system.
The generator owns the files that carry its header. Every generated source file starts with the line // Generated by bpmn-to-code. After generating, the run deletes the files in the target package directory that carry this header and were not produced by this run. Files without the header are never touched.
How to use variantName is described in Modeling, the regenerate-and-diff check in Verify in CI.
Consequences
- A file's API name depends on that file alone. Adding a second file never renames an existing API.
- Renaming or removing a process leaves no stale API behind.
- Two generation runs must not share a package directory: each would delete the other's files, and the shared definition files have fixed names.
- Users can commit generated code and let CI fail on a diff, which only works because output is deterministic.
- The naming rule lives in the BPMN model and is invisible in the build script. A
variantNameon a file that shares its process id with no other file still renames its API. - Files sharing a process id have no common type. Each API carries the same
PROCESS_ID, and code written against several of them names each one. - A cross-model validation rule that looks up a process by id gets the first file with that id.
Rejected alternatives
- Merge files that share a process id into one API. The merged union was hardly used by the generated code, and it was wrong by construction: a node declared differently in two files silently took its attributes from the file that sorted first, and the API grew with every variant. It also forced validation into a phase before and a phase after merging.
- A callback or strategy object that names each file. A function cannot travel through a
pom.xmlor an HTTP request, and as a Gradle task input it is tracked by class identity only. - A name template such as
{variantName}_{processId}. The tool's first mini-language, with error cases of its own, and no way to say that the default model keeps the plain name without a second rule. - A switch that enables the
variantNameprefix. It would keep a strayvariantNamefrom renaming an API, at the price of a setting to plumb through every entry point (record 1). - A new property key for the prefix. A second name for what users already know as the variant.
- A mapping table from variant to prefix. It states the list of variants a second time and takes a different shape in each entry point.
- Version the generated API by file (a suffix
V<n>and a counter file). See record 11.