Bungee jumping to Quarkus

Blindfolded but happy

María Arias de Reyna Domínguez

@delawen

Senior Software Engineer

Who am I?
  • 19🐱🐱 Crazy Cat Lady
  • Free and Open Source Advocate
  • 2008 Software Engineer
  • GeoSpatial world
    • 2009 Dijkstra A* Witch
    • 2012 Metadata Wrangler
    • 2013 OSGeo Charter Member
      • 2017-2019 OSGeo President
  • 2016 Women in Tech
  • Senior Software Engineer at Red Hat
    • 2019 Integration Druid
      • 2019 Apache Camel contributor
      • 2020 Apache Software Foundation
      • 2021 Lead Kaoto Development
  • Java Champion

Kaoto.io

Low code and No code integration editor

Before and After

  • JDK 8~9
  • Regular Java
  • Blocking (Synchronous)
  • War deployed on Tomcat
  • JDK 17
  • Quarkus
  • Reactive
  • Knative deployment
Quarkus in a Nutshell

What is Quarkus?

Pros

  • Incredibly fast
  • Easy dependency management
    • Opinionated: The libraries are chosen for you
  • Reactive (and Imperative)!
  • Kubernetes
    • Native compilation
  • Live Coding
    • Developing and seeing changes in real time
  • Great Design Patterns
    • They are not enforced, but use them, for your own sanity

Cons

  • Fancy State of the Art
    • Good until you do something not implemented yet
      • Which means you can do the next big thing
  • No Reflection
    • Arc allows you to inject at build time
  • Debug in native mode
Quarkus in Action

Quarkus in Action

Dev console dashboard by default

Maven MultiProject

  • Compilation in native mode didn't work... at first
  • Why? They work independently!
  • I followed the quickstart in quarkus.io, you must work!
Error: Substitution target for io.quarkus.runtime.generated.Target_org_jboss_logmanager_Logger is not loaded.
Use field `onlyWith` in the `TargetClass` annotation to make substitution only active when needed.
              
com.oracle.svm.core.util.UserError$UserException: Substitution target for io.quarkus.runtime.generated.
Target_org_jboss_logmanager_Logger is not loaded. Use field `onlyWith` in the `TargetClass` annotation 
to make substitution only active when needed.
              

Maven MultiProject

Don't do native compilation on all of the projects.

Don't be me.

  • Remove the native profile in all the pom.xml
  • Except from the last one
                
                  
                    
                        native
                        
                            
                                native
                            
                        
                        
                            native
                        
                    
                  
                
              

Maven MultiProject

Missing Beans in native mode

  • Errors that only happen in native mode... again
feb 02, 2022 11:22:19 A. M. io.quarkus.runtime.ApplicationLifecycleManager run
ERROR: Failed to start application (with profile prod)
java.lang.ClassNotFoundException: org.objectweb.asm.ClassVisitor
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at io.smallrye.config.ConfigMappingInterface.getClassBytes(ConfigMappingInterface.java:152)
	at io.smallrye.config.ConfigMappingLoader.loadClass(ConfigMappingLoader.java:98)
	at io.smallrye.config.ConfigMappingLoader.getImplementationClass(ConfigMappingLoader.java:81)
	at io.smallrye.config.ConfigMappingLoader$1.computeValue(ConfigMappingLoader.java:20)
	at io.smallrye.config.ConfigMappingLoader$1.computeValue(ConfigMappingLoader.java:17)
	at java.base/java.lang.ClassValue.getFromHashMap(ClassValue.java:228)
	at java.base/java.lang.ClassValue.getFromBackup(ClassValue.java:210)
	at java.base/java.lang.ClassValue.get(ClassValue.java:116)
	at io.smallrye.config.ConfigMappingLoader.configMappingObject(ConfigMappingLoader.java:57)
	at io.smallrye.config.ConfigMappingContext.constructGroup(ConfigMappingContext.java:80)
	at io.kaoto.backend.api.metadata.catalog.ViewDefinitionRepository1270545574Impl.<init>(Unknown Source)
              

Context and Dependency Injections and Bean discovery

Autowired injected beans

                
@ApplicationScoped
public class ClusterService {

   @Inject
    MyOtherBean beanybeany; 

    @Counted  
    String translate(String sentence) {
      System.out.println("Interceptor binding annotation from MicroProfile");
    }
    
    @PostConstruct
    void init() {
      System.out.println("Hello there!");
    }
    
    @PreDestroy
    void init() {
      System.out.println("My revenge will be terrible.");
    }

}
                
              

Context and Dependency Injections and Bean discovery

Injection of lists of beans

  • Note the @Startup
  • Exactly one bean must be assignable to an injection point, otherwise the build fails.
  • This is resolved on build time, you should know better.
  • But you can always inject a list of beans of the same type
  • javax.enterprise.inject.Instance extends Iterable
                
@Startup
@ApplicationScoped
public class StepCatalog extends AbstractCatalog {

    private Instance<StepCatalogParser> stepCatalogParsers;
    
    
    @Inject
    public void setStepCatalogParsers(
            final Instance<StepCatalogParser> stepCatalogParsers) {
        this.stepCatalogParsers = stepCatalogParsers;
    }
    
     @Inject 
    void multipleBeans(OneBean one, AnotherBean another) { 
         this.one = one;
         this.another = another;
    }
}
                
              

Marshall and Unmarshall

Because I like magic

  • Note the @RegisterForReflection
  • Polymorphic unmarshalling
                             
@RegisterForReflection
public class MyObjectDeserializer implements JsonbDeserializer {

    private static final Jsonb JSONB = JsonbBuilder.create();

    @Override
    public MyObject deserialize(final JsonParser parser,
                                 final DeserializationContext context,
                                 final Type rtType) {

        String type = parser.getObject().toString().getString("type");

        switch (type) {
            case "patata":
            ...
            }
}
                
              
                             
@JsonSubTypes({
        @JsonSubTypes.Type(value = Patata.class, name = "patata"),
        @JsonSubTypes.Type(value = Poteito.class, name = "potato")})
//This is a workaround utility class until Quarkus supports fully polymorphism
@JsonbTypeDeserializer(MyObjectDeserializer.class)
public abstract class MyObject implements Cloneable {
...
}
                
              

Reactive

  • Do nothing on the main thread
  • Always promise the future in return
                

    public CompletableFuture<List<T>> doIt() {
        CompletableFuture<List<T>> res = new CompletableFuture<>();
        res.completeAsync(() -> doItSlowly());
        return res;
    }
    
    public void doItAndwait() {
        doIt().join();
    }

                
              

Reactive

What if my endpoint returns a slow response?

  • Or a stream?!!
WARNING [io.ver.cor.imp.BlockedThreadChecker] (vertx-blocked-thread-checker) 
Thread Thread[vert.x-eventloop-thread-10,5,main]=Thread[vert.x-eventloop-thread-10,5,main] has been blocked for 4827 ms,
time limit is 2000 ms: io.vertx.core.VertxException: Thread blocked
              
                
    @NoCache
    @Path("/{name}/logs")
    @Produces(MediaType.TEXT_PLAIN)
    @GET
    @Operation(summary = "Get logs",
            description = "Get the resource's log.")
    @Blocking
    public Multi<String> logs(
            final @Parameter(description = "Name of the resource "
                    + "of which logs should be retrieved.")
            @PathParam("name") String name,
            final @Parameter(description = "Namespace of the cluster "
                    + "where the resource is running.")
            @QueryParam("namespace") String namespace,
            final @Parameter(description = "Number of last N lines to be "
                    + "retrieved.")
            @QueryParam("lines") int lines) {

        return clusterService.streamlogs(namespace, name, lines);

    }
                
              

Tests

Always run them in native mode too

  • Don't forget the @QuarkusTest annotation
  • @TestHTTPEndpoint is your friend
                
@QuarkusTest
@TestHTTPEndpoint(IntegrationResource.class)
class IntegrationResourceTest {

    @Test
    void thereAndBackAgain() throws URISyntaxException, IOException {


        var res = given()
                .when()
                .contentType("application/json")
                .body(Collections.emptyList())
                .post("/dsls")
                .then()
                .statusCode(Response.Status.OK.getStatusCode());
        List<String> dsls =
                mapper.readValue(res.extract().body().asString(), List.class);
        assertFalse(dsls.isEmpty());
    }

}
                
              

Tests

Smart TestContainers

  • Automatic provisioning of unconfigured services
  • Databases, Kubernetes, Messaging,...
  • Configuration (port, host, version?) in properties

Tests

Kubernetes too

  • Via fabric8
                             
@WithKubernetesTestServer
@QuarkusTest
class ClusterServiceTest {
            
    @Inject
    public void setKubernetesClient(final KubernetesClient kubernetesClient) {
        this.kubernetesClient = kubernetesClient;
    }
    
    private KubernetesClient kubernetesClient;
    
}
                
              

Keep Quarkus upgraded

  • Because in the frontier of Development, you sometimes forget you already fixed something

Keep Quarkus upgraded

Add a buffer between the State of the Art and your code

  • Quarkus is moving fast, don't be left behind
                             
        
        quarkus-universe-bom
        io.quarkus
        2.10.2.Final
        ${quarkus.platform.version}
                
              
Questions?