Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/adr/0016-multi-module-packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# ADR 0016 — Multi-module packaging (lean core + render backends + templates)

- **Status:** Accepted
- **Date:** 2026-07-06
- **Authors:** Artem Demchyshyn

## Context

Through the 1.x line GraphCompose shipped as a single published jar,
`io.github.demchaav:graph-compose`. That one artifact bundled the engine, the
PDFBox render backend (plus PDFBox and zxing), the Apache POI semantic backend,
and the built-in template families. A consumer who only wanted the engine — or
who intended to bring their own render backend — still pulled the entire set,
and an engine patch republished everything.

The [API-stability ledger](../api-stability.md#3-deprecation-window) tracked this
as a **Stable** packaging commitment we intended to change in the next major: the
single-jar shape could not be split in a minor without breaking every existing
coordinate.

Two forces shaped the split:

- **Consumer-side leanness is the benefit, not release-side flexibility.** The
goal is a small dependency tree for engine-only / bring-your-own-backend
consumers. Versioning stays **lockstep** — a patch republishes the whole train
— so the split never promises independent per-module releases.
- **PDF back-compatibility is non-negotiable.** Every existing caller depends on
`graph-compose` and calls `…buildPdf()`. Upgrading to 2.0 must not make a bare
`graph-compose` stop rendering PDF.

## Decision

Split the single jar into per-concern Maven modules, versioned in lockstep, with
render backends discovered at runtime through a `ServiceLoader` SPI.

**Coordinates:**

- **`graph-compose-core`** — the lean engine: the `DocumentSession` authoring API,
the canonical DSL / nodes / style / layout, chart / svg / markdown / barcode, and
the `FixedLayoutBackendProvider` / `FontMetricsProvider` SPI seams. Depends only on
`slf4j-api` + `flexmark`. Rendering nothing until a backend is on the classpath —
it throws `MissingBackendException` (naming the artifact to add) if asked to build a
PDF without one.
- **`graph-compose-render-pdf`** — the entire PDFBox backend (`document.backend.fixed.pdf.**`
and the `engine.render.pdf.**` tree), PDFBox, and zxing. Registers the PDF
`FixedLayoutBackendProvider` / `FontMetricsProvider` via `META-INF/services`.
- **`graph-compose`** — kept as a coordinate, but now an **empty jar-wrapper** over
`graph-compose-core` + `graph-compose-render-pdf`. So a 1.x caller who upgrades keeps
rendering PDF with no code and no dependency change. Packaging stays `jar` (not `pom`)
so existing `<dependency>` declarations need no `<type>`.
- **`graph-compose-render-docx`** / **`graph-compose-render-pptx`** — the POI semantic
backends, split apart so a DOCX consumer does not pull the PPTX skeleton (and neither
pulls into the engine).
- **`graph-compose-templates`** — the built-in CV / cover-letter / invoice / proposal
presets. Pure authoring over the canonical DSL, so it depends only on
`graph-compose-core`. **Opt-in:** the `graph-compose` wrapper does not bundle it.
- **`graph-compose-testing`** — the consumer testing support (`LayoutSnapshotAssertions`,
`PdfVisualRegression`).
- **`graph-compose-bundle`** — the batteries-included aggregate: the default PDF stack
(via the wrapper) + templates + the independently-versioned `graph-compose-fonts` and
`graph-compose-emoji` companions. Office backends are not bundled.

The engine sources stay at the repository root and only the **root coordinate** is
renamed `graph-compose` → `graph-compose-core`; the new wrapper is a small module. The
default coordinate `graph-compose` therefore continues to mean "PDF out of the box".

No JPMS: modules carry an `Automatic-Module-Name` only, so a split package across
test-scope jars stays legal on the classpath.

## Consequences

- **Engine-only / bring-your-own-backend consumers get a lean tree** — depend on
`graph-compose-core` and pull neither PDFBox, POI, zxing, nor the templates.
- **Existing PDF callers are unaffected** — `graph-compose` still renders PDF out of the
box; no code change on upgrade.
- **Templates-via-`graph-compose` consumers break** — a caller that reached a preset
through the 1.x single jar must now add `graph-compose-templates` (or
`graph-compose-bundle`). This is the one accepted source break; PDF compatibility took
priority. See the [modules migration guide](../migration/v2.0.0-modules.md).
- **Lockstep versioning, not independent releases** — every 2.0 coordinate shares the
engine version; a patch republishes the train. `graph-compose-fonts` /
`graph-compose-emoji` keep their own lines (they change rarely).
- **A `MissingBackendException` is now the "why won't it render" signal** for a lean
`graph-compose-core`, replacing the pre-split assumption that the backend is always present.
- The legacy `Entity`-Component-System render pipeline moves into `graph-compose-render-pdf`
rather than being deleted; it is dead on the canonical path but still exercised by
test scaffolding, so its removal is deferred beyond 2.0.
2 changes: 1 addition & 1 deletion docs/api-stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ window starts, and its `Status` flips to `deprecated 1.x`.
| Element | Tier now | Status | Why the 1.x shape is a compromise | 2.0 action | ADR | Issue |
|---|---|---|---|---|---|---|
| `DocumentSession.pageMargins(List<PageMarginRule>)` / `PageMarginRule` | Stable | planned | Per-page margins resolve a block's content width by the page it *begins* on (the engine measures each block once, before pagination). A margin that changes the content width therefore does not re-wrap a block mid-flow across a page boundary. | Revisit a page-aware per-line/per-fragment width model so a block can re-wrap when it crosses a margin boundary, if demand warrants. | — | — |
| `io.github.demchaav:graph-compose` single-jar packaging | Stable | planned | The one published jar bundles the engine, the PDFBox render backend, the POI semantic backend, zxing, and the template families, so an engine-only or bring-your-own-backend consumer still pulls all of them. | Split into a lean `graph-compose` core plus sibling artifacts — `graph-compose-render-pdf`, `graph-compose-render-docx`, `graph-compose-templates`, `graph-compose-testing` — with render backends discovered via a `ServiceLoader` SPI. `graph-compose` keeps its coordinate but no longer contains the PDF backend or templates; add `graph-compose-render-pdf` (or the batteries-included `graph-compose-bundle`) to keep PDF output. | | — |
| `io.github.demchaav:graph-compose` single-jar packaging | Stable | **landed 2.0** | The one published jar bundled the engine, the PDFBox render backend, the POI semantic backend, zxing, and the template families, so an engine-only or bring-your-own-backend consumer still pulled all of them. | **Done in 2.0.** Split into per-concern lockstep modules, render backends discovered via a `ServiceLoader` SPI. The root coordinate is renamed `graph-compose-core` (the lean engine); `graph-compose` is kept as a back-compat wrapper over `graph-compose-core` + `graph-compose-render-pdf`, so it still renders PDF out of the box. Templates are opt-in (`graph-compose-templates`); DOCX / PPTX ship in `graph-compose-render-docx` / `-render-pptx`. Migration: [modules guide](migration/v2.0.0-modules.md). | [ADR 0016](adr/0016-multi-module-packaging.md) | — |

---

Expand Down
87 changes: 87 additions & 0 deletions docs/migration/v2.0.0-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Migrating to 2.0 — the module split

GraphCompose 2.0 splits the single `graph-compose` jar into per-concern modules so an
engine-only consumer no longer pulls PDFBox, POI, zxing, and the template families. This
guide covers the **dependency** change. For API-level removals (retired classes, the
`.v2` package rename, `BusinessTheme`), see the [`### Removed` / `### Public API` sections
of the changelog](../../CHANGELOG.md); the rationale is [ADR 0016](../adr/0016-multi-module-packaging.md).

## TL;DR — rendering PDF? Nothing changes

`graph-compose` is still the drop-in default. It is now an empty wrapper over
`graph-compose-core` + `graph-compose-render-pdf`, so a 1.x caller who bumps the version
keeps rendering PDF with **no code and no dependency change**.

```xml
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>2.0.0</version>
</dependency>
```

You only need to change your dependencies if you want a **leaner** tree, or if you reached
a **built-in template** through the single jar (see [the one break](#the-one-break-templates)).

## Which artifact now?

| You want | Depend on | Change from 1.x |
|---|---|---|
| PDF out of the box (the 1.x behaviour) | `graph-compose` | none — same coordinate |
| Batteries-included (PDF + templates + fonts + emoji) | `graph-compose-bundle` | now also carries templates + emoji |
| A lean engine, bring your own backend | `graph-compose-core` | **new** — the renamed root coordinate |
| Built-in CV / cover-letter / invoice / proposal presets | add `graph-compose-templates` | **new** — opt-in, not in `graph-compose` |
| DOCX export | add `graph-compose-render-docx` | POI is no longer in the engine |
| PPTX export | add `graph-compose-render-pptx` | split out of the DOCX artifact |
| Consumer layout/visual test helpers | add `graph-compose-testing` (test scope) | unchanged coordinate |

All 2.0 coordinates share the `graph-compose` version. `graph-compose-fonts` and
`graph-compose-emoji` keep their own version lines, as they have since 1.8.0 / 1.9.0.

## The one break: templates

The built-in presets (`com.demcha.compose.document.templates.**`) moved into the opt-in
`graph-compose-templates` artifact, and the `graph-compose` wrapper does **not** bundle
them. If your 1.x code called a preset through the single jar —

```java
ModernInvoice.create(theme).compose(session); // needs graph-compose-templates in 2.0
```

— add the artifact (the package names are unchanged, so imports stay the same):

```xml
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-templates</artifactId>
<version>2.0.0</version>
</dependency>
```

Or depend on `graph-compose-bundle`, which includes it. This is the only dependency-level
source break in the split — PDF compatibility was kept as the priority.

## `MissingBackendException` — the lean-core signal

`graph-compose-core` on its own renders nothing until a render backend is on the classpath.
Asking it to build a PDF throws `MissingBackendException`, whose message names the artifact
to add:

```
No fixed-layout render backend on the classpath: add the
io.github.demchaav:graph-compose-render-pdf artifact (or the
io.github.demchaav:graph-compose-bundle aggregate, or another provider
implementation) to render, rasterize, or measure a document.
```

Depend on `graph-compose` (or `graph-compose-bundle`) instead of `graph-compose-core` and
the PDF backend is already present. See the
[troubleshooting entry](../troubleshooting.md#missingbackendexception-when-rendering).

## Reference

- [README install matrix](../../README.md#installation) — the same table with copy-paste snippets.
- [ADR 0016 — multi-module packaging](../adr/0016-multi-module-packaging.md) — why the split
is shaped this way, including the back-compat wrapper decision.
- [API stability policy § 3](../api-stability.md#3-deprecation-window) — the packaging entry
in the 2.0 breaking-changes ledger.