-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
User initialization task support #32728
base: main
Are you sure you want to change the base?
Conversation
cc @kdubb |
The pull request does work, but it's currently affected by #32729 |
e21b2bd
to
4416d10
Compare
@Inherited | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface Initialization { |
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.
Is this meant to cause the exit of the application when the task is done?
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.
No
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 just tells quarkus, that the annotated code needs to be executed during initalization.
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.
How is this intended to be different than https://quarkus.io/guides/lifecycle#startup_annotation?
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.
The main differences are the following:
- Init tasks can be globally enabled / disabled.
- Init tasks run before the
StartupEvent
is fired (it's guaranteed that they all run before the app). - Extension services may depend on the completion of an Initialization tasks (before the register the ServiceStartBuildItem).
- Init tasks are mapped to Kubernetes Jobs.
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.
Init tasks can be globally enabled / disabled.
What's the use case for doing this?
Init tasks run before the StartupEvent is fired (it's guaranteed that they all run before the app).
Same question as above :)
Extension services may depend on the completion of an Initialization tasks (before the register the ServiceStartBuildItem).
When would this happen in practice?
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.
An other way to put is that that Initialization
is meant to be used for code that can be optionally run isolated (e.g. as kubernetes jobs or via the cli) to prepare the ground for the actual application. Startup
seems more like a listener to me.
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.
Okay, that's an important distinction.
But the name of the annotation does not convey this at all, it's far too generic IMHO
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 that at Initialization
is a pretty good name:
- It rhymes with
at Startup
- Initialization also implies
before startup
(this is the main distinction with Starutp IMHO).
Everything else has to do with how we use it, so I don't feel that the name should convey these.
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.
My point is that it's far too generic - it does not in anyway convey that this piece of code could be "standalone"
It might be good to record some motivation... @iocanel and I discussed this privately and the feature comes from a pattern we noticed internally in our team. We needed to prepare our DevServices (think integration testing) and we did that with some custom code and QuarkusTestLifecycleManagers. Then we would essentially recreate that in scripts to prepare the same services for production in K8s. It quickly became obvious this was the same code and should be shared instead of being written twice in two different languages. The other realization is that nearly every external service (and therefore DevService) required some amount of preparation. We solved this manually internally by creating a Quarkus CLI application (to be used as a K8s init Job) and rearranging our code to run our tasks at startup (using @startup) or manually via the CLI. This is essentially what Flyway & Liquibase do for databases. When @iocanel implemented the K8s init job feature for Flyway it sparked the discussion for this feature as a similar but more generic service initialization/preparation framework. This PR has serious advantages, the obvious being you don't have to do this manually anymore. Additionally, that the tasks are run before the application is started instead of after @startup fires which can cause timing/expectation issues. |
@iocanel Thanks for working on this! |
Thanks for providing the context @kdubb.
I have no doubt of this. I just want us to have proper naming and documention because I am 100% sure that users will easily be confused on what each piece does when generic names are used |
Maybe |
That is definitely better :) |
I honestly don't feel that |
That is feature in my mind. I am thinking of our users here: If they come across |
Agree, it's easy to get confused, as their purpose is very similar. The part where I disagree is that naming will demistify things. |
If we agree it's easy to get confused, why choose a generic name? |
4416d10
to
df0a6df
Compare
I don't find it generic.
Still prefer |
🎊 PR Preview ca75503 has been successfully built and deployed to https://quarkus-pr-main-32728-preview.surge.sh/version/main/guides/ |
The way I see it, the term initialization phase is pretty overloaded itself. If we are going for something like |
Not sure what you mean by Besides that, our code is already using it, our config properties are already using it (see |
df0a6df
to
1633348
Compare
Let's just agree that we totally disagree :)
Sure, but I am looking at it from a different perspective - I am looking at it from the perspective of the maintainer that answers countless user questions and as a result has become fairly competent at predicting user experience issues. |
I should two things that might not be clear from the conversation above:
|
@manovotn: Thanks for taking the time to share your thoughts. The main reasons are:
Using priorities two enforce the above, is not trivial and it feels like something users will use to shoot their foot. |
It uses the same logic that interceptors have been using for years - there are predefined priorities that determine, roughly, when your interceptor should run. This is used for CDI observers too and could be used here as well. On top of that, assuming this goes into the code, the next ask may be for ordering of these kinds of init tasks (which is BTW exactly how we got to ordering observers in CDI) and suddenly you have the exact same "shoot in the foot" problem/argument.
Not sure I follow here. Your init tasks can even exit when all tasks are completed yet are allowed to run after application code?
Could you clarify in what scenario you need this? I did read the comments but I am still failing to grasp it. |
I provided insight into what our usage of this would be quite a while back and I'm just catching up on the discussion but I think our use case is very valid and would be of use to anybody building applications that go beyond "getting started". Let me take you through a specific example that plagues us today... DevServices provide a nice DX but there is almost no way to initialize them with the required configuration beyond what is provided in the devservices configuration. We use RabbitMQ to the point I contributed the RabbitMQ connector for Quarkus. I knew we needed a complex set of pre-defined exchanges/queues in our application, so I added a lot of this to the capability of RabbitMQ's devservices implementation by way of configuration. Unfortunately, we needed more configuration capability because the SmallRye Reactive Messaging configuration is pretty limited. Rather than attempt to contribute more configuration capability to the RabbitMQ extension (which would be an ever growing list of configuration knobs) we took the route seemingly suggested by @manovotn and others and wrote code that runs during This works but has two major pain points:
The first problem, running initialization after all the extension are already "running" is a nuisance (and a pretty bad one). For the case of "SmallRye Reactive Messaging RabbitMQ", this means that our "unconfigured" dev service instance gets started, then SRRM immediately starts connecting to the RabbitMQ "unconfigured" instance and starts endlessly logging connection errors due to the (required) retry configuration. Finally, we receive the The second problem, running after the application has started, becomes kind of obvious. The application is in a "started" state but it has not completed initialization. It seems to me that the initialization of devservices should be completed before the application is considered to be started. Not doing this causes issues with tests specifically. Essentially we want a "Flyway" for RabbitMQ... A configuration step that is linked to the extension and can contribute to initialization before anybody considers the application "started". If it's a valid case for database initialization then it's a valid case for message brokers. Now repeat the above for Vault... We have the exact same issues with Vault. The Vault extension could never provide enough configuration points for us to configure it properly. We need to ensure RabbitMQ and PostgreSQL are initialized with credentials and then store those in Vault. Clearly this is not something that devservices could provide "configuration" for. What do we do with all this initialization code??? Prior to us building our initialization tasks, we used (as much as we could) the capabilities provided by the devservices implementations, then we added code to Then we had to go and recreate all this configuration for a production app that we ran in two places, via Initialization tasks nicely cleaned all this up. The "aha" moment was when we realized that this same code could be broken out, without any changes, into a Quarkus command line app and run as an Initialization tasks allow us to manage, in code, the initialization of our external services; wether they be "devservices" in Docker or "real services" in Kubernetes. |
Just to add to my already longwinded response... I hope you can see that this becomes a pattern. Each external service needs some initialization for testing and production. Adding a formalized way to achieve this in Quarkus will be a stand out feature that provides an exceptional developer experience that works for development, testing and production. |
One thing I forgot to touch upon was ordering; which is a valid concern @manovotn brings up. We currently have initialization tasks for Vault, RabbitMQ, OpenFGA and Redis. Currently one of the tasks is cross-cutting, Vault. For 'dev' and 'test' the Vault task needs to connect directly to PostgreSQL and RabbitMQ to configure them both for Vault's dynamic credentials. So the task needs to be sure that those containers are running. Currently when running during When formalized as a feature I think must still be true, otherwise you are limiting an initialization task to only working with a specific service. This could be fairly easily remedied by having specific sequence points.
That would allow any initialization task to configure inter-working services as necessary. |
Tasks like flyway, liquibase etc may run isolated from the app (e.g. run once as Kubernetes Jobs). This is something we already do and this PR intends to make this feature available to user init code too. |
What is the status of this pull request? I'm not so sure this API would be very useful. I mean most of the users would not need to execute some logic before the app actually starts and the extensions should make use of the build items and recorders. If I understand it correctly the use case is to guarantee that some logic is executed before the In fact, the current logic in |
@mkouba How is the |
@mkouba That was my point. This PR is essentially working to make all this a "supported feature" via init tasks. Your comment focused on one single aspect of that "running before main" but when viewed as a whole this PR I think is where we end up. |
Well, as I mentioned in the comment the Speaking of this PR - I don't think it's guaranteed that the init tasks will be executed before the Also I don't understand why the |
This conversation is just going around in circles. At this point I'll just exit saying this... Quarkus's tagline starts "A Kubernetes Native Java stack...". App initialization is a huge problem in need of solving (I have detailed many examples already); and indeed Flyway was deemed special enough to get this treatment. This feature aimed to solve that generally and provide first class support for initialization of any service that could be shared across development, testing and (K8s) production. Wether it's this PR or another this feature should land. We are currently rolling our own shared initialization (again as described in detail in previous posts), so I know firsthand how hard it is to figure out; including all the warts that currently exist. Quarkus should help here given it's goals. |
From the little I have observed, I agree. However I have not had the time to dig it, but I am hoping that sometime next month I will. |
I'm sorry I'm very likely missing some important piece of information. However, I don't disagree that Quarkus should help in this area... It would be great if we could (1) summarize all the requirements, (2) describe the current solution (which IMO currently does not guarantee what it declares) and (3) try to propose a new API based on 1. and 2. |
Current solutionThere is a need to perform some environment initialization logic (env init) in a quarkus app. Typically, in order to be able to execute a quarkus app as a Kubernetes Job. In this particular case, we also need a way to exit the app once the env init completes but before the application starts (init-and-exit). An extension can produce an RequirementsWe need a new API to define env init logic in CDI beans.
New APIWe could introduce a new annotation or interface to mark a bean as an "env init task". public interface InitTask {
String getName();
void run();
} User code: @ApplicationScoped
public class MyBean implements InitTask {
public String getName() { return "foo"; }
public void run() {}
} Internal usage: @Inject
Instance<InitTask> tasks;
public void runAll() {
tasks.forEach(InitTask::run);
} |
@mkouba Please read my previous comment (#32728 (comment)), it's a fairly detailed use case
This isn't really the requirement. In order to run a Quarkus application as a Kubernetes Deployment/Statefulset the environment needs initialization. The initialization tasks you propose (i.e. your |
Also, as outlined in my previous comments, these |
That's why I used "Typically".
@iocanel Is this requirement implemented in this PR?
Hm, I'm not sure there is a way to formalize the extensions "start" part. What exactly do you mean? |
As an example, on our pain points is with SmallRye Reactive Messaging. We need to setup the RabbitMQ instance with dynamic credentials before it will work and this requires both the Vault and RabbitMQ containers to be running. Unfortunately SRRM begins attempting its connections before application start which creates a lot of logged errors during startup in dev/test. This means to properly initialize the Vault and RabbitMQ containers, without spurious errors, the Generally speaking this means the |
We also have the same exact problem with Postgres and dynamic credentials. |
From the implementation point of view we could probably just consume the |
That seems correct. Wouldn't others need to then consume some new |
Well, the env init task build steps should also produce |
I'm no expert here. I've contributed quite a bit to Quarkus but that list of build items is... big 😏. Back to the SRRM example, I'll look into if that is consuming |
After a quick glance at the SRRM extension I can't find anything that would seem to order it after even dev services. Maybe that explains why it's initializing so early in the startup sequence. Is |
Found this PR when looking for this type of functionality and it appears to have died on the vine. In my use case I can not use the existing Liquibase extension because our datasources are not static at build time but dynamically read at runtime. However, we wanted to make use of the existing
My own extension seems like the right thing to do currently. However, with this PR I could create my own Liquibase runner that runs at init time and make use of |
Resolves: #32123
Resolves: #34643
The pull request introduces an annotation
@PreStart
that is meant to annotateRunnable
classes to mark that they should run during the initialization phase of the application (before Startup).Updates:
@Initialization
@Initialization
to@PreStart
.