Skip to content
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

Arc: declare a synthetic, runtime-initialized bean as eagerly initialized (if conditions met) #41466

Closed
yrodiere opened this issue Jun 26, 2024 · 9 comments · Fixed by #41810
Closed
Labels
area/arc Issue related to ARC (dependency injection) kind/enhancement New feature or request
Milestone

Comments

@yrodiere
Copy link
Member

yrodiere commented Jun 26, 2024

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, Datasource beans 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.active is set to false"
    • For injected beans, the list of injection points
  • 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:

And see this mind map:

arc-initialize-on-startup

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):

  1. activeIf(condition): tells Arc that this bean is only "active" (~initializable) if the provided condition is met. Default is always active.
  2. 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> (returns true if 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> where Condition is 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 the activeIf condition 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.

@yrodiere yrodiere added the kind/enhancement New feature or request label Jun 26, 2024
@quarkus-bot quarkus-bot bot added the area/arc Issue related to ARC (dependency injection) label Jun 26, 2024
Copy link

quarkus-bot bot commented Jun 26, 2024

/cc @Ladicek (arc), @manovotn (arc), @mkouba (arc)

Copy link

quarkus-bot bot commented Jun 26, 2024

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.

@yrodiere
Copy link
Member Author

yrodiere commented Jun 26, 2024

I think that's the gist of it @Ladicek @manofthepeace @mkouba ... please let me know if anything is missing or unclear :)

@yrodiere yrodiere changed the title Arc: declare a synthetic, runtime-initialized bean as initialized on startup (if conditions met) Arc: declare a synthetic, runtime-initialized bean as eagerly initialized (if conditions met) Jun 26, 2024
@jtama
Copy link
Contributor

jtama commented Jun 26, 2024

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 !)

@manofthepeace
Copy link
Contributor

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 :)

@yrodiere
Copy link
Member Author

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 :)

@yrodiere
Copy link
Member Author

yrodiere commented Jun 26, 2024

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:

  1. have a postgres config profile and an elasticsearch config profile
  2. set quarkus.datasource.active and quarkus.elasticsearch.active accordingly in each profile
  3. 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).

@maxandersen
Copy link
Member

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. #7148

@Ladicek
Copy link
Contributor

Ladicek commented Jul 29, 2024

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.

@quarkus-bot quarkus-bot bot added this to the 3.16 - main milestone Oct 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/arc Issue related to ARC (dependency injection) kind/enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants