-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Extension Structure ADR proposal #48688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Extensions must use a well-defined package structure to avoid split packages. | ||
|
||
==== runtime module | ||
* `io.<quarkus|quarkiverse>.<extension-name>`: Public API. May include subpackages (excluding `spi`). Example: `io.quarkus.cache`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem I think we have is that non-extension packages are in a conflicting namespace with extension packages. Would it be possible to change the recommendation going forward to be something like io.<quarkus|quarkiverse>.ext.<extension-name>...
? Otherwise, we could put all non-extension classes under a single subpackage to minimize the conflict e.g. io.quarkus.core.xxx
. Either way is breaking stuff but would prevent more/worse breakage in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would go with the latter.
Vert.x 2 and 3 (at the beginning) tried to use ext
packages - it didn't fly. Very quickly, the ext
looked odd and inconvenient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case we have a lot of packages to rename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about org.apache.camel.quarkus:camel-quarkus-activemq
? Should we make the examples more generic?
I will have a look tomorrow |
* `deployment-spi`: Contains build-time APIs intended to be reused across multiple extensions. Extensions contributing to or using build items from other extensions should depend on deployment-spi modules. | ||
* `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. | ||
|
||
All these modules are optional. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the runtime
module necessary, if for nothign else but to contain META-INF/quarkus-extension.yaml
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, we have extensions that are not "true" extension, in the sense they don't have a runtime:
For example:
- jms-spi, which only contains a
deployment
module (that should be adeployment-spi
module with the new nomenclature)
We could consider that extension as malformed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't aware of that one, indeed it seems weird
* `deployment` depends on `runtime`, `runtime-dev` (if defined) and `deployment-spi` (if defined). | ||
* `runtime` depends on `spi` (if defined). | ||
* External consumers should rely only on `spi` or `deployment-spi` to be loose-coupled. | ||
* External consumers should rely only on `spi` or `deployment-spi` to be tightly-coupled (forcing the target extension to be present at execution). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence needs to be improved. Are consumers
meant to be other extensions in this context or applications? Also, I assume you mean avoid being tightly-coupled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I need a section about the coupling. It's not clear (it is in my head, but not in the document, and the graph is not rendered in the preview)
Yesterday, I drafted that proposal, and I gave some thought about it last evening (yes, I should have considered doing this in the opposite order). I've two questions:
|
Related (on the linting side): quarkiverse/quarkiverse-devops#231 |
IMO yes (every module should have a JPMS name) - we will have to tackle this eventually and it's better to have it done ahead of time. Perhaps our tooling can set this up automatically?
I think |
The In my personal opinion, public API of an extension should be either in the runtime module, under The runtime part of the extension, which is not a public API, should always be in the I see no reason to expose runtime SPI in a different manner than runtime API. They should both be in the same place. If the runtime API has a reason to delimit an SPI, it can do it in all the traditional manners -- we don't need an extra module in the extension just for a runtime SPI. If we need outside world to be able to depend on a runtime API of an extension without depending on the extension as a whole, the full runtime API of the extension should be in a separate module (again, called My $0.017. |
@Ladicek I think it's mostly a naming issue (what is not a naming issue these days 😄). Let me try to clarify how we’re thinking about the separation between runtime, spi, and potential api modules. API vs. SPIIt’s important to distinguish two concepts (as @dmlloyd said). Here is my attempt:
We agree here: API is about usage, SPI is about participation. The Coupling Dimension(Not this is very badly explained in the draft as @geoand mentioned) Where things get more subtle is around coupling: does the application (or consuming extension) require the extension to be present at runtime?
In other words, this separation is about allowing contributions without forcing runtime presence, more architectural than semantic (and that's my whole problem...). Why not call it api?You suggested that the public API should live in a module called If we were trying to reserve api for consumption (i.e., the user-facing surface) and spi for contribution (i.e., extension points), it makes sense to clearly isolate both. The
That's definitely open for debate and suggestions. What about consumed CDI events?That’s, for me, a great case where things blur: the application can consume events produced by an extension without needing that extension to be present. It feels like a runtime API, but from a dependency standpoint, it behaves more like an SPI (you can consume it, even if the source isn’t there). In those cases, having the event types in the spi module makes sense, not because they are technically SPIs, but because we want to avoid runtime coupling. What about?
WDYT? P.S.: Realizing that this comment is almost as long as the initial ADR... |
+1
+1 |
* `spi`: Contains public APIs intended to be consumed by other extensions or application code. This module should have minimal dependencies. Depending on a spi module does not transitively include the full extension (i.e., it avoids pulling in runtime or deployment logic). | ||
* `deployment`: Contains build-time logic, including BuildSteps and recorder logic. This module may define internal build items. | ||
* `deployment-spi`: Contains build-time APIs intended to be reused across multiple extensions. Extensions contributing to or using build items from other extensions should depend on deployment-spi modules. | ||
* `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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sake of consistency with spi
, which implies it's runtime spi, should it just be dev
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also thinking about adding codestart
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, forgot codestart.
=== Additional Rules | ||
|
||
* Module dependencies must follow strict rules: | ||
* `deployment` depends on `runtime`, `runtime-dev` (if defined) and `deployment-spi` (if defined). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deployment
does not need to depend on runtime-dev
. It may be necessary but there is no general reason for that.
|
||
* Module dependencies must follow strict rules: | ||
* `deployment` depends on `runtime`, `runtime-dev` (if defined) and `deployment-spi` (if defined). | ||
* `runtime` depends on `spi` (if defined). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runtime
may depend on runtime-dev
. And runtime-dev
may appear to depend on runtime
. It really depends on a specific case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our current codebase, in terms of Maven dependencies, runtime-dev
depends on runtime
, which is what's important for a project setup for an extension developer.
In the Quarkus dependency model (which is a hidden magic) runtime
appears to depend and pull in runtime-dev
in dev mode.
It doesn't mean other users have to use dev-mode dependencies in this specific way though.
So in my eyes, SPI is part of API. API is what users are supposed to use. Some usages are consumption, some are participation, but it's all API. If you feel like there's a clear line between consumption and participation, I'll respectfully disagree; outside the most simple cases, participation often requires consumption. In other words, an SPI typically looks like interface Participant {
void doSomething(Context ctx);
} where |
@Ladicek We have an example of clean delimitation (like the info extension). However, let's imagine we can't have a clean cut (and the CDI event is an example of this ambiguity). Should we have an |
Yes indeed, that's what I'm advocating for. |
It seems like a split of api(spi)/runtime could be justified if there could be multiple implementations of that API/SPI, i.e. different extensions implementing them. Otherwise, bundling it all in a single runtime module shouldn't be a big deal, should it? |
If we add the classes into the |
I may have missed it, what's the problem with coupling in cases when there is only one impl of a given API? |
@aloubyansky it's mostly what I tried to explained here: The Coupling Dimension(Not this is very badly explained in the draft as @geoand mentioned) Where things get more subtle is around coupling: does the application (or consuming extension) require the extension to be present at runtime?
In other words, this separation is about allowing contributions without forcing runtime presence, more architectural than semantic (and that's my whole problem...). If we put API+SPI in the
|
Thanks. So strong coupling is still an extension developer choice. |
First draft (following #47074)
Ping: @gsmet @geoand @dmlloyd