quarkus
quarkus copied to clipboard
Arc: declare a synthetic, runtime-initialized bean as eagerly initialized (if conditions met)
Description
Some Quarkus extensions would need Arc to expose the ability to declare a synthetic, runtime-initialized bean as initialized on startup -- under some circumstances.
Briefly, here's the situation:
- By design,
Datasourcebeans are defined at build time, when we have incomplete information about the runtime environment. - Application developers may not actually use all datasources at runtime:
- The default datasource being defined implicitly, it's possble the user didn't even want it in the first place.
- In some cases (e.g. Keycloak) the application is distributed as a binary, thus it must define a static list of datasources (postgres, oracle, mysql, sql server, ...), and people running the binary will decide to use only one of these by activating it.
- Application developers may forget to configure a datasource (set the JDBC URL) at runtime.
And here's the need:
- We want startup to fail if the application contains a user bean that gets injected with an active , unconfigured (no JDBC URL) datasource.
- We want programmatic retrieval of such bean to fail similarly -- on retrieval, not when the bean is actually used!
- We want the failures to include actionable messages:
- The root cause for the problem, specific to each bean: "This datasource is inactive because
quarkus.datasource.activeis set to false" - For injected beans, the list of injection points
- The root cause for the problem, specific to each bean: "This datasource is inactive because
- We want the failure to have a specific exception type, so that programmatic retrieval can catch it and ignore it (e.g. for Agroal metrics/health, or Flyway/Liquibase: those consumers just want to ignore datasources that are not available).
Note that this is not specific to datasources: other extensions need a similar feature (Hibernate ORM) and some may need it in the future (MongoDB client, Elasticsearch client, ...).
For more information about the problem, see:
- https://github.com/quarkusio/quarkus/issues/36666#issuecomment-2162923149
- https://groups.google.com/g/quarkus-dev/c/enMgpOrb61o/m/cRKwiWmGAgAJ
- https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/.22Activate.22.2F.22Deactivate.22.20beans.20at.20runtime
And see this mind map:
Implementation ideas
Here are the conclusions of our last conversation.
The feature below only make sense for runtime-initialized, @ApplicationScoped, synthetic bean definitions. Eager initialization probably doesn't make much sense for singletons -- which are already initialized eagerly and are not proxied -- and for other scopes and pseudo-scopes (how would you initialize a @Dependent or @RequestScoped bean eagerly?).
We could add two methods to io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator (names are placeholders subject to bikeshedding):
activeIf(condition): tells Arc that this bean is only "active" (~initializable) if the provided condition is met. Default is always active.initializeEagerly(): tells Arc that this bean should be initialized:- on startup if it's active (see above) and injected in a user (non-synthetic) bean. Default is to only follow CDI semantics for initialization.
- on first retrieval (no uninitialized proxy) if retrieved programmatically. Default is to only follow CDI semantics for initialization.
The type of condition would be, depending on implementation needs (TBD):
- A
RuntimeValue<BooleanSupplier>(returnstrueif the condition is met) - A
RuntimeValue<Runnable>(checks the condition, if not met throws with meaningful, actionable message) - A combination of the above, e.g.
RuntimeValue<Condition>whereConditionis defined as:public interface Condition extends BooleanSupplier { // returns `true` if the condition is met @Override boolean getAsBoolean(); // checks the condition, if not met throws with meaningful, actionable message void check() throws RuntimeException; }
Additionally, we will need to implement two new behaviors:
- The "eager" initialization on bean retrieval, which will throw a meaningful, typed exception (
InactiveBeanException?) if theactiveIfcondition is not met, or wrap any exception thrown by initialization. - The "eager" initialization on startup, which essentially amounts to startup code that will go through all eagerly-initialized beans, and retrieve those that are injected and active, wrapping any exception with more context (the list of injection point).
In practice, we'll probably use this for datasources by setting the activeIf condition to something like "quarkus.datasource[.name].active is unset OR set to true" -- but it could be more complicated, we'll have to check. We'll also have to adapt some code that currently retrieves the datasources through various ways.
In the future we'll want to trigger initialization on startup even if a bean is only used in other synthetic beans (e.g. a datasource in a Hibernate ORM persistence unit), but that will require more work as we'll want to ignore synthetic consumers that are themselves inactive.
/cc @Ladicek (arc), @manovotn (arc), @mkouba (arc)
You added a link to a Zulip discussion, please make sure the description of the issue is comprehensive and doesn't require accessing Zulip
This message is automatically generated by a bot.
I think that's the gist of it @Ladicek @manofthepeace @mkouba ... please let me know if anything is missing or unclear :)
That would be awsome, we are implementing an application that can work with both postgres OR elastic, and we can't decide at compile time which will be chosen at runtime... Really eager to have this availbale (that would allow us to trash a whole bunch of code !)
I think that's the gist of it @Ladicek @manofthepeace @mkouba ... please let me know if anything is missing or unclear :)
I think that was meant for @manovotn :)
I think that's the gist of it @Ladicek @manofthepeace @mkouba ... please let me know if anything is missing or unclear :)
I think that was meant for @manovotn :)
It was, autocompletion slipped. Sorry :)
That would be awsome, we are implementing an application that can work with both postgres OR elastic, and we can't decide at compile time which will be chosen at runtime... Really eager to have this availbale (that would allow us to trash a whole bunch of code !)
Note this was envisioned strictly as a feature for Quarkus extensions, as applications can't define synthetic beans.
You're right that it could help applications eventually though, since this feature will make it easier to support quarkus.elasticsearch.active. I intended to do that as part of #10905.
Once that's there, I think you will essentially need to:
- have a
postgresconfig profile and anelasticsearchconfig profile - set
quarkus.datasource.activeandquarkus.elasticsearch.activeaccordingly in each profile - make sure to hide the elasticsearch client or datasource behind a CDI producer, which will generate a different implementation of a custom bean of yours (a postgres impl or an elasticsearch impl) based on runtime config. Something like this, except you would use programmatic bean lookup (or your app would just fail to start).
could this be used for case where today @Inject StatelessSession ss still require users to have @Entity on some bean first even though a user of statlesssession does not require any entity.
i.e. https://github.com/quarkusio/quarkus/issues/7148
No, that's a completely different problem.
Here, we're making it possible for a bean to become "inactive", throwing an exception when someone tries to create an instance anyway.