quarkiverse / quarkus-amazon-services

Quarkus Amazon Services extensions
Apache License 2.0
41 stars 49 forks source link

How could I use Quarkus in CDK applications ? #1273

Closed nicolasduminil closed 4 months ago

nicolasduminil commented 5 months ago

Hello,

I'm facing the following case: I have a CDK application class defining a Stack in which I need to use MP Config via Quarkus @ConfigProperty annotations. For example:

public class MyCdkApp
{
  public static void main(String... args)
  {
    App app = new App();
    ...
    new MyStack(app, ...);
    app.synth();
  }
}

The class MyStack is the one where I need to use:

  ...
  @Inject
  @ConfigProperty(defaultValue = "function.zip", name="zip-location")
  String zipLocation;
  ...

Given that the class is manually instantiated by calling its constructor, it isn't handled by Quarkus and, hence, the properties aren't injected. I've tried to inject MyStack into MyCdkApp but running cdk deploy I got This app contains no stacks, showing that, for some reasons, the injection didn't work.

Is there any possibility to either instantiate MyStack via its constructor while being handled by Quarkus (probably not) or to inject it such that to be "seen" by the CDK ?

Many thanks in advance.

Nicolas

nicolasduminil commented 5 months ago

What worked at this point is the programmatic configuration via ConfigProvider.getConfig(). But it doesn't allow to provide default values. I'm still looking for a solution to use injection.

nicolasduminil commented 5 months ago

Anyone there could help please ?

scrocquesel commented 5 months ago

I don't think you need to mix Quarkus with CDK. However, if you want to, I believe your question is specific to Quarkus. Could you open an issue or a discussion at https://github.com/quarkusio/quarkus/?

What I would do is use SmallRye Config as a standalone library. See the example for configuring mapping and default values: https://github.com/smallrye/smallrye-config/tree/main/examples/mapping.

scrocquesel commented 5 months ago

BTW, maybe if you write a Quarkus command line application, you could inject properties and create the stack and app from there. https://quarkus.io/guides/command-mode-reference

nicolasduminil commented 4 months ago

@scrocquesel : "I don't think you need to mix Quarkus and CDK". While I don't mandatory need to do that, it's extremely important to be able to do that. Unless you're saying that CDK isn't supported by Quarkus, in which case please mention it clearly.

Generally speaking, nobody "needs" to use Quarkus, or Spring, or Micronaut, or Helidon, etc., but being able to use them is much better than the opposite :-). And I have to say that it is quite uncommon for a Quarkus project to advise not trying to use Quarkus, but to use instead SmallRye libraries in a stand-alone manner.

As you probably know, using Quarkus with CDK is a very valuable software architecture which gives access to one of the most powerful IaC platform (CDK) and, in the same time, to the one of the most powerful micro-services platform (Quarkus). I really don't see what might be the added value of avoid using Quarkus.

Additionally, using SmallRye as a standalone library won't be of any help because the issue will persist as long as the CDK app is directly and explicitly instantiated and, hence, whatever you do, Quarkus or anything else couldn't handle this instantiation performed outside it. My point was to look for a solution such that to start the CDK app as a Quarkus app, by modifying perhaps the command in the cdk.json file, or something like this. This command is actually:

mvn -e -q compile exec:java

and here is the config of the maven-exec-plugin:

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <configuration>
          <mainClass>fr.simplex_software.aws.iac.cdk.starter.CdkStarterApp</mainClass>
        </configuration>
      </plugin>

So, as you see, the CDK app is started as a simple JAR, not at a Quarkus one and, hence, not being a Quarkus app, it can't take advantge of Quarkus feature, like MP Config, etc. I think that one possibility would be to modify the cdk.json file such that to start the *-runner app, or something like that. And I was looking for advises here ... Then I could take advantage of CDI.

You should probably imagine that, before opening an issue here, I did it at the Quarkusio site and, if I finally landed here, this is because the guys there asked me to do it here. Now, if you send me back to them, then the circle is closed :-).

Thank you very much for providing me with this link to the documentation but it isn't relevant, for the same reason mention above.

scrocquesel commented 4 months ago

Thank you for the very detailled explanation. This issue is not related to this extension. What your are looking for is how to run a quarkus application from command line. So, you just need to package the project and run the quarkus jar.

After having created a quarkus command line application with the tutorial, move your cdk classes to the app, add the required package dependencies, and copy the cdk.json file to the quarkus project. Edit the cdk.json and change the app config value: "app": "mvn package && java -jar target/quarkus-app/quarkus-run.jar",

then, you can run cdk ls and see something like

INFO] 
[INFO] --- quarkus:3.11.0:build (default) @ command-mode-quickstart ---
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 1826ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.294 s
[INFO] Finished at: 2024-06-04T23:06:40+02:00
[INFO] ------------------------------------------------------------------------
[WARNING] 
[WARNING] Plugin validation issues were detected in 1 plugin(s)
[WARNING] 
[WARNING]  * org.apache.maven.plugins:maven-resources-plugin:3.3.0
[WARNING] 
[WARNING] For more or less details, use 'maven.plugin.validation' property with one of the values (case insensitive): [BRIEF, DEFAULT, VERBOSE]
[WARNING] 
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-06-04 23:06:41,279 INFO  [io.quarkus] (main) command-mode-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.11.0) started in 0.443s. 
2024-06-04 23:06:41,288 INFO  [io.quarkus] (main) Profile prod activated. 
2024-06-04 23:06:41,288 INFO  [io.quarkus] (main) Installed features: [cdi]
2024-06-04 23:06:42,703 INFO  [io.quarkus] (main) command-mode-quickstart stopped in 0.010s

MyProjectStack
nicolasduminil commented 4 months ago

@scrocquesel : Many thanks for your help.

"then, you can run cdk ls ..." Not sure how do you run that as the Quarkus app is executing in foreground and, hence, you can't run any command. And trying to run it in another terminal raises this exception:

Another CLI (PID=37570) is currently synthing to cdk.out. Invoke the CLI in sequence, or use '--output' to synth into different directories.

Another issue is that running cdk destroy ... starts the Quarkus app as well, which is not was in intended.

scrocquesel commented 4 months ago

You don't need to start the quarkus app first. Cdk is basically a wrapper that set up env variables and start the app command. The app then write the deployment manifest to disk and cdk read it. This is what I understand

nicolasduminil commented 4 months ago

@scrocquesel : I'm running the following command:

$ cdk deploy --all

This looks in the cdk.json file for the app: element which is

mvn clean package && java -jar target/quarkus-app/quarkus-run.jar

The Quarkus app starts, here is the entrypoint:

@QuarkusMain
public class CdkApiGatewayApp implements QuarkusApplication
{
  ...
  @Override
  public int run(String... args) throws Exception
  {
     @Produces
     App app = new App();

      ...
     app.synth();
     Quarkus.waitForExit();
     return 0;
  }
}

I'm getting that:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.061 s
[INFO] Finished at: 2024-06-06T19:13:33+02:00
[INFO] ------------------------------------------------------------------------
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-06-06 19:13:34,270 INFO  [io.quarkus] (main) ... on JVM (powered by Quarkus 3.11.0) started in 0.274s. 
2024-06-06 19:13:34,275 INFO  [io.quarkus] (main) Profile prod activated. 
2024-06-06 19:13:34,275 INFO  [io.quarkus] (main) Installed features: [cdi]

That's all. Quarkus.waitForExit() waits for CTRL-C to stop. If I don't call it in the code above, then the app finishes as soon as started. If I call it, it runs in foreground and the cdk deploy statement never finishes.

I tried to have

mvn clean package && java -jar target/quarkus-app/quarkus-run.jar &

in the cdk.json file. It doesn't seem like a good idea and, additionally, then the app starts when I run both cdk deploy and cdk destroy.

scrocquesel commented 4 months ago

waitfor exit is only needed if you run a background thread. Let the app return after tu the app.synth

nicolasduminil commented 4 months ago

As I said, I need to call waitForExit() such that to avoid the app to exit as soon as started. For example, the following code:

@QuarkusMain
public class CdkApiGatewayApp implements QuarkusApplication
{
  ...
  @Override
  public int run(String... args) throws Exception
  {
     @Produces
     App app = new App();

      ...
     app.synth();
     Quarkus.waitForExit();
     return 0;
  }

}

displays:

Press [e] to edit command line args (currently '-jar target/quarkus-app/quarkus-run.jar'), [h] for more options>
Tests paused
Jun 08, 2024 7:12:14 PM io.quarkus.bootstrap.runner.Timing printStartupTime
INFO: cdk-quarkus-api-gateway 0.1 on JVM (powered by Quarkus 3.11.0) started in 0.857s. 
INFO: Profile dev activated. Live Coding activated.
INFO: Installed features: [cdi]
**INFO: cdk-quarkus-api-gateway stopped in 0.003s**
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
Press [space] to restart, [e] to edit command line args (currently '-jar target/quarkus-app/quarkus-run.jar'), [r] to resume  testing, [o] Toggle test output, [h] for more options>
scrocquesel commented 4 months ago

As I said, I need to call waitForExit() such that to avoid the app to exit as soon as started. For example, the following code:

@QuarkusMain
public class CdkApiGatewayApp implements QuarkusApplication
{
  ...
  @Override
  public int run(String... args) throws Exception
  {
     @Produces
     App app = new App();

      ...
     app.synth();
     Quarkus.waitForExit();
     return 0;
  }

}

displays:

Press [e] to edit command line args (currently '-jar target/quarkus-app/quarkus-run.jar'), [h] for more options>
Tests paused
Jun 08, 2024 7:12:14 PM io.quarkus.bootstrap.runner.Timing printStartupTime
INFO: cdk-quarkus-api-gateway 0.1 on JVM (powered by Quarkus 3.11.0) started in 0.857s. 
INFO: Profile dev activated. Live Coding activated.
INFO: Installed features: [cdi]
**INFO: cdk-quarkus-api-gateway stopped in 0.003s**
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
Press [space] to restart, [e] to edit command line args (currently '-jar target/quarkus-app/quarkus-run.jar'), [r] to resume  testing, [o] Toggle test output, [h] for more options>

This is the dev mode experience with mvn quarkus:dev. You cannot use this mode with CDK. The code of the CDK npm package is open source; read it a bit, and you will understand how it works. It is a wrapper that starts an app with some environment variables and waits for it to exit after writing a manifest file to exploit this manifest file. If the environment variables are not set, the app exits doing nothing as expected.

The quarkus:dev mode is a wrapper that launches the app and never ends, allowing you to run tests and restart the app. I have given you all the information needed to make things work.

With what I told you to do:

[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.958 s
[INFO] Finished at: 2024-06-08T19:31:33+02:00
[INFO] ------------------------------------------------------------------------
[WARNING]
[WARNING] Plugin validation issues were detected in 1 plugin(s)
[WARNING]
[WARNING]  * org.apache.maven.plugins:maven-resources-plugin:3.3.0
[WARNING]
[WARNING] For more or less details, use 'maven.plugin.validation' property with one of the values (case insensitive): [BRIEF, DEFAULT, VERBOSE]
[WARNING]
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2024-06-08 19:31:34,288 INFO  [io.quarkus] (main) command-mode-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.11.0) started in 0.433s.
2024-06-08 19:31:34,297 INFO  [io.quarkus] (main) Profile prod activated.
2024-06-08 19:31:34,297 INFO  [io.quarkus] (main) Installed features: [cdi]
2024-06-08 19:31:35,771 INFO  [io.quarkus] (main) command-mode-quickstart stopped in 0.008s

✨  Synthesis time: 11.49s

Unable to resolve AWS account to use. It must be either configured when you define your CDK Stack, or through the environment
import com.myorg.MyProjectApp;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class JavaMain {

    public static void main(String... args) {
        Quarkus.run(MyProjectApp.class, args);
    }
}
package com.myorg;

import software.amazon.awscdk.App;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.StackProps;

import java.util.Arrays;

import io.quarkus.runtime.QuarkusApplication;

public class MyProjectApp implements QuarkusApplication {
        public void main(final String[] args) {
                App app = new App();

                new MyProjectStack(app, "MyProjectStack", StackProps.builder()
                                .build());

                app.synth();
        }

        @Override
        public int run(String... args) throws Exception {
                main(args);
                return 0;
        }
}
nicolasduminil commented 4 months ago

"This is the dev mode experience with mvn quarkus:dev."

It looks like it, as a matter of fact, but I don't know why is that as I didn't run mvn quarkus:dev.

The code of the CDK npm package is open source; read it a bit, and you will understand how it works.

Well, I don't think that reading the code would be the right way here.

In order to avoid any possible confusion, please use this reproducer here (https://github.com/nicolasduminil/quarkus-s3). The name isn't well chosen but it illustrates my point. Run it as follows:

$ git clone https://github.com/nicolasduminil/quarkus-s3
$ cd quarkus-s3 
$ mvn package
$ cdk deploy

You'll see what I mean. Please feel free to modify it in order to make it work.

Please notice also that you can't use statements like this one:

new MyProjectStack(app, "MyProjectStack", StackProps.builder().build());

if you want to use CDI further.

Many thanks in advance.

Nicolas

nicolasduminil commented 4 months ago

@scrocquesel : Any comment ?

scrocquesel commented 4 months ago

@scrocquesel : Any comment ?

I'll try to have a look soon.

scrocquesel commented 4 months ago

@scrocquesel : Any comment ?

I'll try to have a look soon.

I'll create a pr on your repo. You defintely need to read more about the framework you use. I changed the scope of some bean to Singleton to avoid the proxy bean to call the base constructor more than once and also for the stack to properly call the ctor when injected. If it were a proxy (with @ApplicationScoped), , the ctor will not be called and the stack will not be added to the app. I also move the producer to a dedicated class to avoid stackoverflow due to the cyclic dependency of the bean.

Hope this will help you move forward.

nicolasduminil commented 4 months ago

I'll create a PR ...

Great, looking forward for it. When do you think you'll be doing that ?

You'll definitely need to read more ...

In my opinion, the role of a contributor isn't to comment on the issuer lack of knowledge, but to help to fix the issue. Which you're doing, of course, but you really don't need this kind of comments.

nicolasduminil commented 4 months ago

My bad, I've seen you've already created the PR, sorry for not having realized that. I confirm that, thanks to the modifications you've brought, everything works now as expected. Many thanks again for your help and support. Closing the issue.

Kind regards, Nicolas

nicolasduminil commented 4 months ago

@scrocquesel : Hi, I'm so sorry but I have to reopen once again this issue because something isn't right here, as far as CDI is concerned. I modified the master branch of the project to add some MP Config in the CdkApiGatewayStack class and, as you can see if you run it, it doesn't work.

More precisely, the following sequence:

  @Inject
  @ConfigProperty(name = "prop", defaultValue = "my-prop")
  String prop;
  ...
  if (prop == null)
    System.out.println (">>> Prop is null");
  else
    System.out.println (">>> Prop is not null " + prop);
  ...

displays:

>>> Prop is null

I'm not sure why is that and, for having used a lot MP Config, I didn't see this issue yet. I persist to think that it must be something related to the peculiarities of the AWS CDK extensions. But I might be mistaken, in which case I'm relying on you to help me, without criticizing me for my lack of knowledge.

Many thanks in advance.

Kind regards,

Nicolas

scrocquesel commented 4 months ago

@scrocquesel : Hi, I'm so sorry but I have to reopen once again this issue because something isn't right here, as far as CDI is concerned. I modified the master branch of the project to add some MP Config in the CdkApiGatewayStack class and, as you can see if you run it, it doesn't work.

More precisely, the following sequence:

  @Inject
  @ConfigProperty(name = "prop", defaultValue = "my-prop")
  String prop;
  ...
  if (prop == null)
    System.out.println (">>> Prop is null");
  else
    System.out.println (">>> Prop is not null " + prop);
  ...

displays:

>>> Prop is null

I'm not sure why is that and, for having used a lot MP Config, I didn't see this issue yet. I persist to think that it must be something related to the peculiarities of the AWS CDK extensions. But I might be mistaken, in which case I'm relying on you to help me, without criticizing me for my lack of knowledge.

Many thanks in advance.

Kind regards,

Nicolas

The result is as expected. The ctor is executed, THEN, the properties are injected. Use ctor injection. If you don't want to have a bunch of @ConfigProperty injected arguments, I would recommend the use of a configuration object and @Inject it.

Please, for anything not directly related to the AWS clients SDK this project supports, you should ask the Quarkus main project. A lot more people could help you with these basics.

nicolasduminil commented 4 months ago

The ctor is executed, THEN, the properties are injected.

Got it, thanks.