Skip to content

2.0: restructure jmsfx as a monorepo with independent lifecycles #102

Description

@ctgnz

One repository, seven flat modules, one version. A consumer sees only jmsfx-core and one generated library, but a change to the Browse page bumps the version they pin.

The original plan here was to split into separate repositories. That is the wrong shape for this project: almost every issue in 1.5.0 was cross-cutting — #68 touched fragments, model and generated code; #78, #79 and #80 touched all three across both libraries; #90 touched generator, server and standard. Split, each of those becomes a sequence of coordinated pull requests with version bumps in between. #106 is the evidence in the other direction: bringing jmsfx-hallux in made things simpler, and the fragment checks now cover both trees precisely because they share a repository.

So: one repository, grouped by deliverable, with lifecycles that can move independently.

Six top-level modules

module contains what it is
jmsfx-core — the API. Everything pins it, so it should move slowly
jmsfx-tools jmsfx-generator, jmsfx-editor build-time tooling: produces libraries, and edits the models they come from
jmsfx-viewer jmsfx-creator, jmsfx-server applications, consuming a library like anyone else
jmsfx-standard — the generated APP-6E library
jmsfx-hallux — the hallux extension library
jmsfx-battleorder — the Battle Order extension library, moving in from its own repository

jmsfx-tools and jmsfx-viewer are parent and aggregator poms for their children, which is what makes the grouping do real work: within a group ${project.version} still applies, so the generator and the editor move together as one thing; between groups a dependency pins an explicit released version, so bumping it is a decision.

The editor belongs with the generator rather than with the applications because it edits the model file, which is the generator's input. The dependency graph hides that — the coupling runs through the YAML, and #7 is what will make it explicit.

What crosses a boundary

module depends on
jmsfx-standard, jmsfx-hallux, jmsfx-battleorder jmsfx-core
tools / jmsfx-editor jmsfx-core, and jmsfx-generator in-group
viewer / jmsfx-creator jmsfx-core, a library at runtime
viewer / jmsfx-server a library — #93, still a hard compile dependency on jmsfx-standard

So jmsfx-core's version is pinned in four or five places. That is the deliberate bump this is for.

The cost, stated plainly

Once jmsfx-standard declares jmsfx-core:2.0.0 rather than ${project.version}, Maven resolves that exact GAV and will not substitute the 2.1.0-SNAPSHOT beside it in the tree. That is what lifecycle independence means — but it follows that a change to core is no longer tested against standard until standard's dependency is deliberately bumped, and mvn verify at the root stops proving the tree is coherent.

Fair when cadences differ. Pure overhead when they do not. So do not split every lifecycle at once: the signal is a module that is stable while others churn, and jmsfx-core is the obvious first, being the API everything pins.

Mechanics

  • Directory moves plus a three-level parent chain, both unremarkable.
  • Today's pom.xml is both parent and aggregator. Separating them lets the parent sit on a stable version of its own, so bumping it does not mean editing five <parent><version> blocks.
  • Release tags per module — jmsfx-core-2.0.0 — with workflows triggered on tag patterns publishing only that module.
  • If jmsfx-generator publishes, jmsfx-tools must publish too, or resolution breaks for exactly the reason the root parent does. Same trap as Publish jmsfx to Maven Central from the release workflow #103's parent. excludeArtifacts becomes jmsfx-editor, jmsfx-viewer, jmsfx-creator, jmsfx-server, with jmsfx-generator coming off the list.

One build product per library

jmsfx-creator and jmsfx-server no longer compile against any library (#76, #93), so which one they ship with is a packaging decision. Each gets a Maven profile per library, and each profile produces a distinct artifact, distinguished by classifier — one build product per library rather than one artifact whose contents depend on how it was built.

Two things settled while working out how:

No activeByDefault. A default-activated profile is switched off the moment any other profile is named on the command line, and #103 put mvn -Prelease deploy in the release workflow. A standard profile relying on default activation would silently deactivate during a release and publish a server with no library in it. The failure is quiet and late: the jar starts, then answers 500 to every request with "No IconLibrary on the classpath". So the library is always named explicitly — -Pstandard, -Phallux, -Pbattleorder — and a build that names none should fail rather than guess.

runtime, not runtime + optional. spring-boot:repackage leaves optional dependencies out of BOOT-INF/lib, so an optional library never reaches the fat jar. optional is the wrong tool regardless — it stops a dependency reaching consumers, and nothing consumes an application as a library. runtime alone is what prevents anything compiling against it. See #93.

The deployment then becomes one instance per library on its own *.ctg.co.nz subdomain, each running the artifact carrying its classifier. #111 is what makes that verifiable from outside: today a running instance cannot say which library it has.

Not a reason to do this

A consumer pinned to jmsfx-core:2.0.0 is already fully insulated from later work, whatever the repository looks like. The shared version costs a version number that moves without content changing — cosmetic, not a stability problem. The pain that prompted this was jmsfx-hallux depending on jmsfx-core:1.5.0-SNAPSHOT, and a snapshot means "whatever is in the reactor right now". #103 gave it something to pin and #106 put it where it can.

Prerequisites

Still open

Where the SVG fragments live. jmsfx-standard and jmsfx-hallux each carry a full copy — 2,972 and 3,286, the shared ones byte-identical — and jmsfx-battleorder will want them too. One repository makes that visible and puts both under the same checks, but does not fix it. #82 does, by injecting fragments into the generated classes so a library carries what it needs.

Related: #7, #81, #82, #93, #101, #103, #106, #108, #109, #111.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions