Skip to content

Commit 6b07891

Browse files
authored
Merge pull request #48688 from cescoffier/adr-extension-structure
2 parents 99633a2 + 7578b22 commit 6b07891

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

adr/0009-extension-structure.adoc

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
= Extension Structure Guidelines
2+
3+
* Status: Accepted
4+
* Date: 2025-12-11
5+
* Authors: @cescoffier
6+
7+
== Context
8+
9+
The Quarkus ecosystem includes more than 800 extensions, many of which were developed without a consistent structure.
10+
The Quarkus core repository alone contains over 150 extensions.
11+
12+
Most extensions follow the basic runtime/deployment split, but the boundaries between API and internal logic have often blurred.
13+
Additionally, without clear package structure rules, many split packages have emerged.
14+
This situation introduces several issues:
15+
16+
* It is unclear which classes are intended as public APIs versus internal implementations.
17+
* There is no clear boundary between what an extension consumer can depend on and what they should not use.
18+
* Some extensions mix SPI and implementation code, reducing modularity and increasing the chance of unwanted coupling.
19+
* The need for build item re-use or coordination between extensions leads to ad-hoc patterns.
20+
21+
In this ADR, an extension refers to a coherent unit of functionality that contributes one or more modules (typically Maven artifacts) to the Quarkus ecosystem and can be consumed via the Quarkus extension catalog or platform BOM.
22+
23+
More recently, we introduced dev-only modules, further increasing complexity.
24+
25+
To address these issues and prepare for long-term maintainability (and possibly Java Platform Module System (JPMS) adoption), we propose a more structured layout for extension modules.
26+
27+
== Decision
28+
29+
Extensions may adopt a standardized structure, composed of one mandatory and several optional modules, depending on their complexity and integration needs:
30+
31+
* `runtime`: Contains the runtime implementation.
32+
It may expose public APIs if needed but should prefer delegation to the `runtime-api` module (See <<the-place-of-the-public-api>>).
33+
This module also contains the `recorders` and other runtime logic.
34+
* `runtime-api`: Contains public APIs intended to be consumed by other extensions or application code.
35+
This module should have minimal dependencies.
36+
Depending on an `runtime-api` module does not transitively include the full extension (i.e., it avoids pulling in runtime or build logic). See <<coupling-api-and-spi>> for more details.
37+
* `deployment`: Contains build-time logic, including `BuildSteps`.
38+
This module may define internal build items.
39+
* `deployment-spi`: Contains build-time APIs intended to be reused across multiple extensions for integration purposes.
40+
Extensions contributing to or using build items from other extensions should depend on `build-spi` modules.
41+
* `runtime-dev`: Contains runtime classes used exclusively in dev mode (e.g., for the Dev UI). This avoids shipping dev-only classes into production artifacts.
42+
* `codestart`: Contains the _Codestart_ templates for the extension. See https://quarkus.io/guides/extension-codestart[Codestart Guide] for more details.
43+
* `cli`: Contains CLI commands related to the extension. This is optional and should be used only if the extension provides specific CLI functionality.
44+
* `codegen`: Contains code generation utilities such as code pre-processors invoked at build time.
45+
46+
Only the `runtime` and `deployment` modules are mandatory; the others are optional based on the extension's needs.
47+
48+
49+
.New structure for Quarkus extensions
50+
[source,tree]
51+
----
52+
my-extension/
53+
├── runtime/
54+
├── runtime-api/
55+
├── deployment/
56+
├── deployment-spi/
57+
├── runtime-dev/
58+
├── codegen/
59+
├── cli/
60+
└── codestart/
61+
----
62+
63+
NOTE: We considered renaming `deployment` and `deployment-spi` to `build` and `build-spi`. We decided to not include that change in this ADR. It will be revisited in another ADR.
64+
65+
=== Additional Rules
66+
67+
Module dependencies must follow strict rules:
68+
69+
* `deployment` depends on `runtime`, and `deployment-spi` (if defined).
70+
* `runtime` depends on `runtime-api` (if defined).
71+
* External consumers should rely only on `runtime-api` or `deployment-spi` to be loose-coupled (See <<coupling-api-and-spi>>.).
72+
* External consumers should rely only on `runtime` or `deployment` to be tightly-coupled (forcing the target extension to be present at execution). See <<coupling-api-and-spi>>.
73+
* Public APIs must be explicitly documented, including build items meant for inter-extension use.
74+
* Public build items must document their intended usage (produced, consumed, or both).
75+
* Each module should provide an `Automatic-Module-Name` based on the root package.
76+
Using the `Automatic-Module-Name` allows extensions to be used as JPMS modules in the future, even if we do not fully adopt JPMS yet.
77+
Also, it avoids split packages, as the `Automatic-Module-Name` must be unique across all modules in the Quarkus ecosystem:
78+
79+
- Runtime: `io.<quarkus|quarkiverse>.<extension-name>`
80+
- Deployment: `io.<quarkus|quarkiverse>.<extension-name>.deployment`
81+
- Runtime API: `io.<quarkus|quarkiverse>.<extension-name>.api`
82+
- Deployment SPI: `io.<quarkus|quarkiverse>.<extension-name>.deployment.spi`
83+
- Runtime-Dev: `io.<quarkus|quarkiverse>.<extension-name>.dev`
84+
- Codegen: `io.<quarkus|quarkiverse>.<extension-name>.codegen`
85+
- CLI: `io.<quarkus|quarkiverse>.<extension-name>.cli`
86+
87+
* Each module should have artifact idents following the pattern:
88+
89+
- Runtime: `quarkus-<extension-name>`
90+
- Deployment: `quarkus-<extension-name>-deployment`
91+
- Runtime API: `quarkus-<extension-name>-api`
92+
- Deployment SPI: `quarkus-<extension-name>-deployment-spi`
93+
- Runtime Dev: `quarkus-<extension-name>-dev`
94+
- Codegen: `quarkus-<extension-name>-codegen`
95+
- Codestart: `quarkus-<extension-name>-codestart`
96+
- CLI: `quarkus-<extension-name>-cli`
97+
98+
[#coupling-api-and-spi]
99+
=== Coupling, API and SPI
100+
101+
Historically, Quarkus introduced `spi` modules to separate public APIs from internal implementation details.
102+
However, the distinction between these modules has often been unclear, leading to confusion about their intended use.
103+
104+
There is also a _coupling_ dimension that needs to be taken into account when defining the extension structure.
105+
106+
==== The Coupling Dimension
107+
108+
An extension using another extension can be either strongly or loosely coupled, depending on whether it requires the other extension to be present at runtime:
109+
110+
* Strongly coupled: An extension directly depends on another extension's runtime module (and its `deployment` on the other extension's `deployment` module), requiring it to be present.
111+
* Loosely coupled: An extension only depends on the public API or SPI of another extension, allowing it to be used without requiring the full extension.
112+
113+
When an extension depends on another extension, it should clearly indicate whether it is tightly or loosely coupled:
114+
115+
* Tightly coupled: The extension depends on the `runtime` or `deployment` module of another extension, indicating that it requires the full extension to be present.
116+
* Loosely coupled: The extension depends on the `runtime-api` or `deployment-spi` module of another extension, indicating that it can work independently of the full extension. Note that this may require conditional logic to handle the absence of the extension at runtime.
117+
118+
An extension can also decide to only support _tight-coupling_ and does not provide a `runtime-api` or `deployment-spi` module.
119+
However, it is strongly recommended to provide at least a `deployment-spi` module to allow other extensions to integrate with it and `runtime-api` to allow other extensions to use its public API without forcing the full extension to be present.
120+
121+
NOTE: Because of the integration nature of the build items located in the `deployment-spi` module, we recommend keeping `deployment-spi` as the name.
122+
123+
[#the-place-of-the-public-api]
124+
==== The place of the public API
125+
126+
Historically, extensions have often placed their public APIs in the `runtime` module. This forces a tight coupling between the extension and its consumers, as they must depend on the `runtime` module to access the public API.
127+
128+
To clarify this, we propose the following rules:
129+
130+
* Public APIs should be placed in a dedicated `runtime-api` module, which can be used independently of the full extension.
131+
* The `runtime` module should focus on the internal implementation and runtime logic, delegating public APIs to the `runtime-api` module.
132+
* The `runtime-api` module contains the public API allowing other extensions to depend on it without pulling in the full extension.
133+
134+
For extensions requiring tight-coupling, the `runtime` module can still be used to expose public APIs, but this should be avoided when possible.
135+
136+
IMPORTANT: Once an extension has a `runtime-api` module, it should not expose public APIs in the `runtime` module. This avoids confusion and ensures that consumers can clearly distinguish between public APIs and internal implementation details. Also, it would not be possible to move the public API back into the `runtime` module.
137+
138+
[cols="1,3,2",options="header"]
139+
|===
140+
| Scenario | Recommended Structure | Notes
141+
142+
| Small/simple extension not reused by others
143+
| `runtime`
144+
| Keep everything in `runtime`. Avoid unnecessary modularity.
145+
146+
| Extension exposes public types used by application code or other extensions
147+
| `runtime-api` + `runtime`
148+
| Split APIs (annotations, interfaces, utility classes) into `runtime-api`. Keep internal logic in `runtime`.
149+
150+
| Extension contributes dev-mode-only logic (e.g., Dev UI)
151+
| `runtime` + `runtime-dev`
152+
| Add `runtime-dev` to isolate dev-only classes. Avoid shipping to production.
153+
154+
| Extension defines types meant to be implemented by others (e.g., customizers, listeners)
155+
| `runtime-api` + `runtime`
156+
| Consider these types part of the API. Place them in `runtime-api` to allow loose coupling.
157+
158+
| Extension wants to enforce tight coupling (full extension must be present)
159+
| `runtime` only
160+
| Expose public types directly from `runtime`. Use cautiously; limits flexibility and reuse.
161+
162+
| Extension depends on or provides code generation
163+
| `runtime` + `codegen`
164+
| Keep codegen logic isolated. Optional, depending on feature set.
165+
|===
166+
167+
=== Package Name Rules
168+
169+
Extensions must use a well-defined package structure to avoid split packages.
170+
171+
[NOTE]
172+
====
173+
In this section, rules are given for the `io.quarkus` and `io.quarkiverse` namespaces.
174+
When the root is different, the rules apply similarly, replacing `io.quarkus` or `io.quarkiverse` with the appropriate root package.
175+
For example: The `org.apache.camel.quarkus.component` package would use the `org.apache.camel.quarkus.component.runtime`, `org.apache.camel.quarkus.component.dev`, etc. packages.
176+
====
177+
178+
==== Root package name
179+
180+
To transform an extension name into a package name, use the following pattern:
181+
182+
* `io.quarkus.<extension-name>` for Quarkus core extensions.
183+
* `io.quarkiverse.<extension-name>` for Quarkiverse extensions.
184+
* If the extension name contains a hyphen, it is recommended to replace it with an underscore (e.g., `quarkus-foo-bar` becomes `io.quarkus.foo_bar`).
185+
186+
==== `runtime` module
187+
* `io.<quarkus|quarkiverse>.<extension-name>.runtime.internal|impl`: Internal implementation. Not part of the public API.
188+
* `io.<quarkus|quarkiverse>.<extension-name>.runtime.graal`: GraalVM substitutions. Not part of the public API.
189+
* `io.<quarkus|quarkiverse>.<extension-name>`: Public API when requiring tight-coupling. May include subpackages (excluding `api` and `dev` packages). Example: `io.quarkus.cache`. Note that this is discouraged in favor of the `runtime-api` module.
190+
191+
Non-API packages should be under `.impl` or `internal` like `io.quarkus.<extension-name>.runtime.internal` or `io.quarkus.<extension-name>.runtime.impl`.
192+
Thus, automated tooling can easily use this information to help generate a `module-info` with the correct exports, as well as excluding those packages from JavaDoc generation.
193+
194+
==== `deployment` module
195+
* `io.<quarkus|quarkiverse>.<extension-name>.deployment`: Internal build logic (processors, build steps, internal build items). Public SPIs must reside in the `deployment-spi` module. May include subpackages (excluding `spi`).
196+
197+
==== `deployment-spi` module
198+
* `io.<quarkus|quarkiverse>.<extension-name>.deployment.spi`: Public build items and types. Considered public API and subject to compatibility guarantees.
199+
200+
Build items in this module should be documented with their intended use (produced or consumed).
201+
202+
==== `runtime-api` module
203+
* `io.<quarkus|quarkiverse>.<extension-name>.api`: Public runtime API. Can be used independently of the full extension. Consumers should not expect the full extension to be available at runtime. To make sure the extension is available, the consumer should use the `runtime` module (which would pull in the `runtime-api` module transitively).
204+
205+
==== `runtime-dev` module
206+
* `io.<quarkus|quarkiverse>.<extension-name>.dev`: Dev-mode-only runtime classes, e.g., for Dev UI contribution. Not included in production builds.
207+
208+
Whether dev-only artifacts are present in a given build remains an explicit choice controlled by the build tooling and extension configuration; this ADR does not change that behaviour.
209+
210+
==== `codegen` module
211+
212+
* `io.<quarkus|quarkiverse>.<extension-name>.codegen`: Code generation logic, if applicable. This module is optional and may not be present in all extensions.
213+
214+
==== `cli` module
215+
216+
* `io.<quarkus|quarkiverse>.<extension-name>.cli`: `quarkus` CLI plugin
217+
218+
When an extension also provides a Quarkus CLI plugin, CLI-specific code should live in a dedicated `cli` module and must not depend on the extension’s `runtime` module.
219+
220+
=== Module Summary Table
221+
222+
[cols="1,3,2",options=“header"]
223+
|===
224+
| Module | Purpose | Intended Consumers
225+
226+
| runtime
227+
| Runtime logic and extension internals
228+
| Application code, Quarkus runtime
229+
230+
| runtime-api
231+
| Public runtime APIs and service provider types
232+
| Other extensions, libraries
233+
234+
| deployment
235+
| Build steps, processor logic, internal build items
236+
| Quarkus build system
237+
238+
| deployment-spi
239+
| Shared build-time APIs (build items, metadata)
240+
| Other extensions
241+
242+
| runtime-dev
243+
| Dev mode–only logic (e.g., Dev UI contributions)
244+
| Development-time only, not production
245+
246+
| codegen
247+
| Code generation logic (if applicable)
248+
| Quarkus build system
249+
250+
| cli
251+
| `quarkus` CLI plugin
252+
| The Quarkus CLI
253+
|===
254+
255+
== Consequences
256+
257+
=== Positive
258+
259+
* Improves long-term maintainability and clarity of the codebase.
260+
* Clarifies the public API surface and encourages proper separation of concerns.
261+
* Avoids the creation of split packages and internal dependency leakage.
262+
* Lays the groundwork for potential future adoption of JPMS (Java Platform Module System).
263+
264+
=== Negative
265+
266+
* Adds structural complexity, which may feel unnecessary for simple extensions.
267+
* Refactoring existing extensions to adopt this structure requires engineering effort.
268+
* New contributors must become familiar with the module layout and associated conventions.
269+
* Some refactoring could break existing extensions and applications, requiring careful migration strategies.
270+
271+
272+
About the last point, extensions can gradually adopt the new structure by first extracting public APIs into a new `runtime-api` module while keeping existing consumers functional. Marking existing runtime types as internal via javadoc or annotations (@Deprecated) can help guide migration.
273+
274+
== Alternatives Considered
275+
276+
* Continuing the current loose structure:
277+
Rejected due to increasing maintenance costs and risk of regressions. After 7 years of evolution, Quarkus needs clearer extension boundaries to remain sustainable.
278+
* Immediate adoption of JPMS (Java Modules):
279+
Deemed too complex and premature. While structurally compatible with this proposal, full JPMS adoption is deferred to avoid breaking changes and complexity in build tooling.
280+
* Renaming `deployment` and `deployment-spi` into `build` and `build-spi`, but it would have been a massive change that need deeper exploration (`build` is the Gradle output directory, it would increase the number of artifacts in the BOM dramatically (leading to Maven performance issues)...)
281+
282+
Note that this ADR is orthogonal to the existing platform BOMs and version alignment mechanisms. Those remain the primary tools to control dependency graphs and compatibility at the platform level; the structure proposed here focuses on how individual extensions are organized internally.
283+
284+
== Related Discussions
285+
286+
* https://github.com/quarkusio/quarkus/discussions/47074[Discussion: Modular Extension Structure]
287+
288+
== Notes
289+
290+
This ADR is forward-looking and prescriptive for new extensions or extensions undergoing significant refactoring. It does not require retrofitting all existing extensions immediately. Tooling, documentation, and examples will progressively support the adoption of this structure. The goal is consistency, clarity, and better long-term modularity within the Quarkus ecosystem.

0 commit comments

Comments
 (0)