Carlos Aguni

Highly motivated self-taught IT analyst. Always learning and ready to explore new skills. An eternal apprentice.


Building Microservices with Quarkus

31 Aug 2021 »

  • Microservices
  • Microprofile
  • Environment:
    • java 11 openjdk
    • graalvm
      • extension of jvm
      • supports several languages
      • creates native binaries
        • static analysis to find reachable code
        • ahread-of-time (aot) compilation
    • editors
      • intellij
      • eclipse
      • vscode
    • maven, gradle
# graalvm
wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.2.0/graalvm-ce-java11-linux-amd64-21.2.0.tar.gz
tar xzvf graalvm-ce-java11-linux-amd64-21.2.0.tar.gz
mv graalvm-ce-java11-21.2.0 /opt/
export GRAALVM_HOME=/opt/graalvm-ce-java11-21.2.0/
echo 'export GRAALVM_HOME=/opt/graalvm-ce-java11-21.2.0/' >> ~/.bashrc

# maven
wget https://www-us.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz -P /tmp
wget https://ftp.unicamp.br/pub/apache/maven/maven-3/3.8.2/binaries/apache-maven-3.8.2-bin.tar.gz
tar xzvf apache-maven-3.8.2-bin.tar.gz 
mv apache-maven-3.8.2 /opt/
export M2_HOME=/opt/apache-maven-3.8.2/
export MAVEN_HOME=/opt/apache-maven-3.8.2/
export PATH="$MAVEN_HOME/bin:${PATH}"
mvn -version
java -version
history
mvn -U io.quarkus:quarkus-maven-plugin:create \
    -DprojectGroupId=org.agoncal.quarkus.microservices \
    -DprojectArtifactId=rest-book \
    -DclassName="org.agoncal.quarkus.microservices.book.BookResource" \
    -Dpath="/api/books" \
    -Dextensions="resteasy-jsonb, smallrye-openapi"
mvn -U io.quarkus:quarkus-maven-plugin:create \
        -DprojectGroupId=org.agoncal.quarkus.microservices \
        -DprojectArtifactId=rest-number \
        -DclassName="org.agoncal.quarkus.microservices.number.NumberResource" \
        -Dpath="/api/numbers" \
        -Dextensions="resteasy-jsonb, smallrye-openapi"

NumberResource.java

package org.agoncal.quarkus.microservices.number;

import org.jboss.logging.Logger;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.Instant;
import java.util.Random;

@Path("/api/numbers")
public class NumberResource {

    @Inject
    Logger logger;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public IsbnNumbers generateIsbnNumbers(){
        IsbnNumbers isbnNumbers = new IsbnNumbers();
        isbnNumbers.isbn13 = "13-" + new Random().nextInt(100_000_000);
        isbnNumbers.isbn10 = "10-" + new Random().nextInt(100_000);
        isbnNumbers.generationDate = Instant.now();
        logger.info("Numbers generated " + isbnNumbers);
        return isbnNumbers;
    }
}

IsbnNumbers.java

package org.agoncal.quarkus.microservices.number;

import java.time.Instant;

public class IsbnNumbers {
    public String isbn13;
    public String isbn10;
    public Instant generationDate;

    @Override
    public String toString() {
        return "IsbnNumbers{" +
                "isbn13='" + isbn13 + '\'' +
                ", isbn10='" + isbn10 + '\'' +
                ", generationDate=" + generationDate +
                '}';
    }
}

  • /q/openapi
  • /q/openapi -H “accept: application/json”
  • /q/swagger-ui
  • mvn quarkus:dev
  • mvn quarkus:add-extension -Dextensions="rest-client"
  • mvn quarkus:add-extension -Dextensions="smallrye-fault-tolerance"
  • mvn package -Dmaven.test.skip=true
  •   mvn package -Dquarkus.package.type=native \
                  -Dmaven.test.skip=true \
                  -Dquarkus.native.container-build=true
    
  • mvn quarkus:add-extension -Dextensions="docker"
  •   mvn package -Dquarkus.package.type=native \
                  -Dmaven.test.skip=true \
                  -Dquarkus.native.container-build=true \
                  -Dquarkus.container-image.build=true
    
  • test
    • curl -X POST http://localhost:8702/api/books -d "title=Quarkus&author=Antonio&year=2021&genre=IT"
    • curl http://localhost:8701/api/numbers
  • references
    • https://quarkus.io/community
    • https://quarkus.io/guides
    • https://github.com/quarkusio
    • https://github.com/quarkiverse
    • https://microprofile.io
    • https://smallrye.io

native build

BookResource.java

package org.agoncal.quarkus.microservices.book;

import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.logging.Logger;

import javax.inject.Inject;
import javax.json.bind.JsonbBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.time.Instant;

@Path("/api/books")
@Tag(name = "Book REST Endpoint")
public class BookResource {

    @Inject
    @RestClient
    NumberProxy proxy;

    @Inject
    @RestClient
    DjangoProxy django_proxy;

    @Inject
    Logger logger;


    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Operation(
        summary = "Creates a Book",
        description = "Creates a Book with an ISBN number"
    )
    @Retry(maxRetries = 3, delay=3000)
    @Fallback(fallbackMethod = "fallingbackCreateABook")
    public Response createABook(@FormParam("title") String title,
                                @FormParam("author") String author,
                                @FormParam("year") int yearOfPublication,
                                @FormParam("genre") String genre){
            Book book = new Book();
            //book.isbn13 = "13-We will get this later from number microservice";
            book.isbn13 = proxy.generateIsbnNumbers().isbn13;
            book.price = django_proxy.generateNumber().django_number;
            book.title = title;
            book.author = author;
            book.yearOfPublication = yearOfPublication;
            book.genre = genre;
            book.creationDate = Instant.now();
            //logger.info("Book created : " + book);
            return Response.status(201).entity(book).build();
    }

    public Response fallingbackCreateABook(String title,
                                           String author,
                                           int yearOfPublication,
                                           String genre){
        Book book = new Book();
        //book.isbn13 = "13-We will get this later from number microservice";
        book.isbn13 = "Will be set later";
        book.title = title;
        book.author = author;
        book.yearOfPublication = yearOfPublication;
        book.genre = genre;
        book.creationDate = Instant.now();
        saveBookOnDisk(book);
        //logger.warn("Book saved on disk : " + book);
        return Response.status(206).entity(book).build();
    }

    private void saveBookOnDisk(Book book){
        String bookJson = JsonbBuilder.create().toJson(book);
        try (PrintWriter out = new PrintWriter("book-" + Instant.now().toEpochMilli() + ".json")) {
            out.println(bookJson);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

DjangoProxy.java

package org.agoncal.quarkus.microservices.book;

import org.agoncal.quarkus.microservices.book.djangoNumber;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.print.attribute.standard.Media;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@RegisterRestClient(configKey = "django.proxy")
@Path("/challenges/proxy")
public interface DjangoProxy {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    djangoNumber generateNumber();
}

DjangoNumber.java

package org.agoncal.quarkus.microservices.book;

import javax.json.bind.annotation.JsonbProperty;

public class djangoNumber {

    @JsonbProperty("django_number")
    public String django_number;
}

application.properties

quarkus.http.host=0.0.0.0
quarkus.http.port=8080
django.proxy/mp-rest/uri=http://pod-quarkus2-lab:8000

Caveats

bind 0.0.0.0

https://stackoverflow.com/questions/55043764/how-to-make-quarkus-to-listen-on-all-network-interfaces-instead-of-localhost