quarkus-panache Archives - Piotr's TechBlog https://piotrminkowski.com/tag/quarkus-panache/ Java, Spring, Kotlin, microservices, Kubernetes, containers Mon, 24 Nov 2025 07:45:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 quarkus-panache Archives - Piotr's TechBlog https://piotrminkowski.com/tag/quarkus-panache/ 32 32 181738725 MCP with Quarkus LangChain4j https://piotrminkowski.com/2025/11/24/mcp-with-quarkus-langchain4j/ https://piotrminkowski.com/2025/11/24/mcp-with-quarkus-langchain4j/#respond Mon, 24 Nov 2025 07:45:15 +0000 https://piotrminkowski.com/?p=15845 This article shows how to use Quarkus LangChain4j support for MCP (Model Context Protocol) on both the server and client sides. You will learn how to serve tools and prompts on the server side and discover them in the Quarkus MCP client-side application. The Model Context Protocol is a standard for managing contextual interactions with […]

The post MCP with Quarkus LangChain4j appeared first on Piotr's TechBlog.

]]>
This article shows how to use Quarkus LangChain4j support for MCP (Model Context Protocol) on both the server and client sides. You will learn how to serve tools and prompts on the server side and discover them in the Quarkus MCP client-side application. The Model Context Protocol is a standard for managing contextual interactions with AI models. It provides a standardized way to connect AI models to external data sources and tools. It can help with building complex workflows on top of LLMs.

This article is the second part of a series describing some of the Quarkus AI project’s most notable features.  Before reading this article, I recommend checking out two previous parts of the tutorial:

You can also compare Quarkus’ support for MCP with similar support on the Spring AI side. You can find the article I mentioned on my blog here.

Source Code

Feel free to use my source code if you’d like to try it out yourself. To do that, you must clone my sample GitHub repository. Then you should only follow my instructions.

Architecture for the Quarkus MCP Scenario

Let’s start with a diagram of our application architecture. Two Quarkus applications act as MCP servers. They connect to the in-memory database and use Quarkus LangChain4j MCP Server support to expose @Tool methods to the MCP client-side app. The client-side app communicates with the OpenAI model. It includes the tools exposed by the server-side apps in the user query to the AI model. The person-mcp-server app provides @Tool methods for searching persons in the database table. The account-mcp-server is doing the same for the persons’ accounts.

quarkus-mcp-arch

Build an MCP Server with Quarkus

Both MCP server applications are similar. They connect to the H2 database via the Quarkus Panache ORM extension. Both provide MCP API via Server-Sent Events (SSE) transport. Here’s a list of required Maven dependencies:

<dependencies>
  <dependency>
    <groupId>io.quarkiverse.mcp</groupId>
    <artifactId>quarkus-mcp-server-sse</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-orm-panache</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-h2</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
XML

Let’s start with the person-mcp-server application. Here’s the @Entity class for interacting with the person table. It uses Panache support to avoid the need for getter and setter declarations.

@Entity
public class Person extends PanacheEntityBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    public String firstName;
    public String lastName;
    public int age;
    public String nationality;
    @Enumerated(EnumType.STRING)
    public Gender gender;

}
Java

The PersonRepository class contains a single method for searching persons by their nationality:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {

    public List<Person> findByNationality(String nationality) {
        return find("nationality", nationality).list();
    }

}
Java

Next, prepare the “tools service” that searches for a single person by ID or a list of people of a given nationality in the database. Each method must be annotated with @Tool and include a description in the description field. Quarkus LangChain4j does not allow a Java List to be returned, so we need to wrap it using a dedicated Persons object.

@ApplicationScoped
public class PersonTools {

    PersonRepository personRepository;

    public PersonTools(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @Tool(description = "Find person by ID")
    public Person getPersonById(
            @ToolArg(description = "Person ID") Long id) {
        return personRepository.findById(id);
    }

    @Tool(description = "Find all persons by nationality")
    public Persons getPersonsByNationality(
            @ToolArg(description = "Nationality") String nationality) {
        return new Persons(personRepository.findByNationality(nationality));
    }
}
Java

Here’s our List<Person> wrapper:

public class Persons {

    private List<Person> persons;

    public Persons(List<Person> persons) {
        this.persons = persons;
    }

    public List<Person> getPersons() {
        return persons;
    }

    public void setPersons(List<Person> persons) {
        this.persons = persons;
    }
}
Java

The implementation of the account-mcp-server application is essentially very similar. Here’s the @Entity class for interacting with the account table. It uses Panache support to avoid the need for getter and setter declarations.

@Entity
public class Account extends PanacheEntityBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    public String number;
    public int balance;
    public Long personId;

}
Java

The AccountRepository class contains a single method for searching accounts by person ID:

@ApplicationScoped
public class AccountRepository implements PanacheRepository<Account> {

    public List<Account> findByPersonId(Long personId) {
        return find("personId", personId).list();
    }

}
Java

Once again, the list inside the “tools service” must be wrapped by the dedicated object. The single method annotated with @Tool returns a list of accounts assigned to a given person.

@ApplicationScoped
public class AccountTools {

    AccountRepository accountRepository;

    public AccountTools(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Tool(description = "Find all accounts by person ID")
    public Accounts getAccountsByPersonId(
            @ToolArg(description = "Person ID") Long personId) {
        return new Accounts(accountRepository.findByPersonId(personId));
    }

}
Java

The person-mcp-server starts on port 8082, while the account-mcp-server listens on port 8081. To change the default HTTP, use the quarkus.http.port property in your application.properties file.

Build an MCP Client with Quarkus

Our application interacts with the OpenAI chat model, so we must include the Quarkus LangChain4j OpenAI extension. In turn, to integrate the client-side application with MCP-compliant servers, we need to include the quarkus-langchain4j-mcp extension.

<dependencies>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-jackson</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-openai</artifactId>
    <version>${quarkus.langchain4j.version}</version>
  </dependency>
  <dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-mcp</artifactId>
    <version>${quarkus.langchain4j.version}</version>
  </dependency>
</dependencies>
XML

The sample-client Quarkus app interacts with both the person-mcp-server app and the account-mcp-server app. Therefore, it defines two AI services. As with a standard AI application in Quarkus, those services must be annotated with @RegisterAiService. Then we define methods and prompt templates, also with annotations @UserMessage or @SystemMessage. If a given method is to use one of the MCP servers, it must be annotated with @McpToolBox. The name inside the annotation corresponds to the MCP server name set in the configuration properties. The PersonService AI service visible below uses the person-service MCP server.

@ApplicationScoped
@RegisterAiService
public interface PersonService {

    @SystemMessage("""
        You are a helpful assistant that generates realistic person data.
        Always respond with valid JSON format.
        """)
    @UserMessage("""
        Find persons with {nationality} nationality.
        Output **only valid JSON**, no explanations, no markdown, no ```json blocks.
        """)
    @McpToolBox("person-service")
    Persons findByNationality(String nationality);

    @SystemMessage("""
        You are a helpful assistant that generates realistic person data.
        Always respond with valid JSON format.
        """)
    @UserMessage("How many persons come from {nationality} ?")
    @McpToolBox("person-service")
    int countByNationality(String nationality);

}
Java

The AI service shown above corresponds to this configuration. You need to specify the MCP server’s name and address. As you remember, person-mcp-server listens on port 8082. The client application uses the name person-service here, and the standard endpoint to MCP SSE for Quarkus is /mcp/sse. To explore the solution itself, it is also worth enabling logging of MCP requests and responses.

quarkus.langchain4j.mcp.person-service.transport-type = http
quarkus.langchain4j.mcp.person-service.url = http://localhost:8082/mcp/sse
quarkus.langchain4j.mcp.person-service.log-requests = true
quarkus.langchain4j.mcp.person-service.log-responses = true
Plaintext

Here is a similar implementation for the AccountService AI service. It interacts with the MCP server configured under the account-service name.

@ApplicationScoped
@RegisterAiService
public interface AccountService {

    @SystemMessage("""
        You are a helpful assistant that generates realistic data.
        Return a single number.
        """)
    @UserMessage("How many accounts has person with {personId} ID ?")
    @McpToolBox("account-service")
    int countByPersonId(int personId);

    @UserMessage("""
        How many accounts has person with {personId} ID ?
        Return person name, nationality and a total balance on his/her accounts.
        """)
    @McpToolBox("account-service")
    String balanceByPersonId(int personId);

}
Java

Here’s the corresponding configuration for that service. No surprises.

quarkus.langchain4j.mcp.account-service.transport-type = http
quarkus.langchain4j.mcp.account-service.url = http://localhost:8081/mcp/sse
quarkus.langchain4j.mcp.account-service.log-requests = true
quarkus.langchain4j.mcp.account-service.log-responses = true
Plaintext

Finally, we must provide some configuration to integrate our Quarkus application with Open AI chat model. It assumes that Open AI token is available as the OPEN_AI_TOKEN environment variable.

quarkus.langchain4j.chat-model.provider = openai
quarkus.langchain4j.log-requests = true
quarkus.langchain4j.log-responses = true
quarkus.langchain4j.openai.api-key = ${OPEN_AI_TOKEN}
quarkus.langchain4j.openai.timeout = 20s
Plaintext

We can test individual AI services by calling endpoints provided by the client-side application. There are two endpoints GET /count-by-person-id/{personId} and GET /balance-by-person-id/{personId} that use LLM prompts to calculate number of persons and a total balances amount of all accounts belonging to a given person.

@Path("/accounts")
public class AccountResource {

    private final AccountService accountService;

    public AccountResource(AccountService accountService) {
        this.accountService = accountService;
    }

    @POST
    @Path("/count-by-person-id/{personId}")
    public int countByPersonId(int personId) {
        return accountService.countByPersonId(personId);
    }

    @POST
    @Path("/balance-by-person-id/{personId}")
    public String balanceByPersonId(int personId) {
        return accountService.balanceByPersonId(personId);
    }

}
Java

MCP for Promps

MCP servers can also provide other functionalities beyond just tools. Let’s go back to the person-mcp-server app for a moment. To share a prompt message, you can create a class that defines methods returning the PromptMessage object. Then, we must annotate such methods with @Prompt, and their arguments with @PromptArg.

@ApplicationScoped
public class PersonPrompts {

    final String findByNationalityPrompt = """
        Find persons with {nationality} nationality.
        Output **only valid JSON**, no explanations, no markdown, no ```json blocks.
        """;

    @Prompt(description = "Find by nationality.")
    PromptMessage findByNationalityPrompt(@PromptArg(description = "The nationality") String nationality) {
        return PromptMessage.withUserRole(new TextContent(findByNationalityPrompt));
    }

}
Java

Once we start the application, we can use Quarkus Dev UI to verify a list of provided tools and prompts.

Client-side integration with MCP prompts is a bit more complex than with tools. We must inject the McpClient to a resource controller to load a given prompt programmatically using its name.

@Path("/persons")
public class PersonResource {

    @McpClientName("person-service")
    McpClient mcpClient;
    
    // OTHET METHODS...
    
    @POST
    @Path("/nationality-with-prompt/{nationality}")
    public List<Person> findByNationalityWithPrompt(String nationality) {
        Persons p = personService.findByNationalityWithPrompt(loadPrompt(nationality), nationality);
        return p.getPersons();
    }
    
    private String loadPrompt(String nationality) {
        McpGetPromptResult prompt = mcpClient.getPrompt("findByNationalityPrompt", Map.of("nationality", nationality));
        return ((TextContent) prompt.messages().getFirst().content().toContent()).text();
    }
}
Java

In this case, the Quarkus AI service should not define the @UserMessage on the entire method, but just as the method argument. Then a prompt message is loaded from the MCP server and filled with the nationality parameter value before sending to the AI model.

@ApplicationScoped
@RegisterAiService
public interface PersonService {

    // OTHER METHODS...
    
    @SystemMessage("""
        You are a helpful assistant that generates realistic person data.
        Always respond with valid JSON format.
        """)
    @McpToolBox("person-service")
    Persons findByNationalityWithPrompt(@UserMessage String userMessage, String nationality);

}
Java

Testing MCP Tools with Quarkus

Quarkus provides a dedicated module for testing MCP tools. We can use after including the following dependency in the Maven pom.xml:

<dependency>
  <groupId>io.quarkiverse.mcp</groupId>
  <artifactId>quarkus-mcp-server-test</artifactId>
  <scope>test</scope>
</dependency>
XML

The following test verifies the MCP tool methods provided by the person-mcp-server application. The McpAssured class allows us to use SSE, streamable, and WebSocket test clients. To create a new client for SSE, invoke the newConnectedSseClient() static method. After that, we can use one of several available variants of the toolsCall(...) method to verify the response returned by a given @Tool.

@QuarkusTest
public class PersonToolsTest {

    ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testGetPersonsByNationality() {
        McpAssured.McpSseTestClient client = McpAssured.newConnectedSseClient();
        client.when()
                .toolsCall("getPersonsByNationality", Map.of("nationality", "Denmark"),
                        r -> {
                            try {
                                Persons p = mapper.readValue(r.content().getFirst().asText().text(), Persons.class);
                                assertFalse(p.getPersons().isEmpty());
                            } catch (JsonProcessingException e) {
                                throw new RuntimeException(e);
                            }
                        })
                .thenAssertResults();
    }

    @Test
    public void testGetPersonById() {
        McpAssured.McpSseTestClient client = McpAssured.newConnectedSseClient();
        client.when()
                .toolsCall("getPersonById", Map.of("id", 10),
                        r -> {
                            try {
                                Person p = mapper.readValue(r.content().getFirst().asText().text(), Person.class);
                                assertNotNull(p);
                                assertNotNull(p.id);
                            } catch (JsonProcessingException e) {
                                throw new RuntimeException(e);
                            }
                        })
                .thenAssertResults();
    }
}
Java

Running Quarkus Applications

Finally, let’s run all our quarkus applications. Go to the mcp/account-mcp-server directory and run the application in development mode:

$ cd mcp/account-mcp-server
$ mvn quarkus:dev
ShellSession

Then do the same for the person-mcp-server application.

$ cd mcp/person-mcp-server
$ mvn quarkus:dev
ShellSession

Before running the last sample-client application, export the OpenAI API token as the OPEN_AI_TOKEN environment variable.

$ cd mcp/sample-client
$ export OPEN_AI_TOKEN=<YOUR_OPENAI_TOKEN>
$ mvn quarkus:dev
ShellSession

We can verify a list of tools or prompts exposed by each MCP server application by visiting its Quarkus Dev UI console. It provides a dedicated “MCP Server tile.”

quarkus-mcp-devui

Here’s a list of tools provided by the person-mcp-server app via Quarkus Dev UI.

Then, we can switch to the sample-client Dev UI console. We can verify and test all interactions with the MCP servers from our client-side app.

quarkus-mcp-client-ui

Once all the sample applications are running, we can test the MCP communication by calling the HTTP endpoints exposed by the sample-client app. Both person-mcp-server and account-mcp-server load some test data on startup using the import.sql file. Here are the test API calls for all the REST endpoints.

$ curl -X POST http://localhost:8080/persons/nationality/Denmark
$ curl -X POST http://localhost:8080/persons/count-by-nationality/Denmark
$ curl -X POST http://localhost:8080/persons/nationality-with-prompt/Denmark
$ curl -X POST http://localhost:8080/accounts/count-by-person-id/2
$ curl -X POST http://localhost:8080/accounts/balance-by-person-id/2
ShellSession

Conclusion

With Quarkus, creating applications that use MCP is not difficult. If you understand the idea of tool calling in AI, understanding the MCP-based approach is not difficult for you. This article shows you how to connect your application to several MCP servers, implement tests to verify the elements shared by a given application using MCP, and support on the Quarkus Dev UI side.

The post MCP with Quarkus LangChain4j appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2025/11/24/mcp-with-quarkus-langchain4j/feed/ 0 15845
Introduction to gRPC with Quarkus https://piotrminkowski.com/2023/09/15/introduction-to-grpc-with-quarkus/ https://piotrminkowski.com/2023/09/15/introduction-to-grpc-with-quarkus/#respond Fri, 15 Sep 2023 23:11:34 +0000 https://piotrminkowski.com/?p=14508 In this article, you will learn how to implement and consume gRPC services with Quarkus. Quarkus provides built-in support for gRPC through the extension. We will create a simple app, which uses that extension and also interacts with the Postgres database through the Panache Reactive ORM module. You can compare gRPC support available in Quarkus […]

The post Introduction to gRPC with Quarkus appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to implement and consume gRPC services with Quarkus. Quarkus provides built-in support for gRPC through the extension. We will create a simple app, which uses that extension and also interacts with the Postgres database through the Panache Reactive ORM module. You can compare gRPC support available in Quarkus with Spring Boot by reading the following article on my blog. This is a good illustration of what Quarkus may simplify in your development.

Source Code

If you would like to try it by yourself, you can always take a look at my source code. In order to do that, you need to clone my GitHub repository. It contains several different Quarkus apps. For the current article, please refer to the person-grpc-service app. You should go to that directory and then just follow my instructions 🙂

Generate Model Classes and Services for gRPC

In the first step, we will generate model classes and gRPC services using the .proto manifests. The same as for the Spring Boot app we will create the Protobuf manifest and place it inside the src/main/proto directory. We need to include some additional Protobuf schemas to use the google.protobuf.* package (1). Our gRPC service will provide methods for searching persons using various criteria and a single method for adding a new person (2). Those methods will use primitives from the google.protobuf.* package and model classes defined inside the .proto file as messages. The Person message represents a single model class. It contains three fields: idname, age and gender (3). The Persons message contains a list of Person objects (4). The gender field inside the Person message is an enum (5).

syntax = "proto3";

package model;

option java_package = "pl.piomin.quarkus.grpc.model";
option java_outer_classname = "PersonProto";

// (1)
import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";

// (2)
service PersonsService {
  rpc FindByName(google.protobuf.StringValue) returns (Persons) {}
  rpc FindByAge(google.protobuf.Int32Value) returns (Persons) {}
  rpc FindById(google.protobuf.Int64Value) returns (Person) {}
  rpc FindAll(google.protobuf.Empty) returns (Persons) {}
  rpc AddPerson(Person) returns (Person) {}
}

// (3)
message Person {
  int64 id = 1;
  string name = 2;
  int32 age = 3;
  Gender gender = 4;
}

// (4)
message Persons {
  repeated Person person = 1;
}

// (5)
enum Gender {
  MALE = 0;
  FEMALE = 1;
}

Once again I will refer here to my previous article about Spring Boot for gRPC. With Quarkus we don’t need to include any Maven plugin responsible for generating Java classes. This feature is automatically included by the Quarkus gRPC module. This saves a lot of time – especially at the beginning with Java gRPC (I know this from my own experience). Of course, if you want to override a default behavior, you can include your own plugin. Here’s the example from the official Quarkus docs.

Now, we just need to build the project with the mvn clean package command. It will automatically generate the following list of classes (I highlighted the two most important for us):

By default, Quarkus generates our gRPC classes in the target/generated-sources/grpc directory. Let’s include it as the source directory using the build-helper-maven-plugin Maven plugin.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>add-source</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>target/generated-sources/grpc</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>

Dependencies

By default, the quarkus-grpc extension relies on the reactive programming model. Therefore we will include a reactive database driver and a reactive version of the Panache Hibernate module. It is also worth adding the RESTEasy Reactive module. Thanks to that, we will be able to run e.g. Quarkus Dev UI, which also provides useful features for the gRPC services. Of course, we are going to write some JUnit tests to verify core functionalities, so that’s why quarkus-junit5 is included in the Maven pom.xml.

<dependencies>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-grpc</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive-panache</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
  </dependency>
  <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

Using the Quarkus gRPC Extension

Once we included all the required libraries and generated classes for Protobuf integration we can with the implementation. Let’s begin with the persistence layer. We already have message classes generated, but we still need to create an entity class. Here’s the PersonEntity class. We will take advantage of PanacheEntity, and thanks to that we don’t need to e.g. define getters/setters.

@Entity
public class PersonEntity extends PanacheEntity {

    public String name;
    public int age;
    public Gender gender;

}

Thanks to the Quarkus Panache field access rewrite, when your users read person.name they will actually call your getName() accessor, and similarly for field writes and the setter. This allows for proper encapsulation at runtime as all the fields calls will be replaced by the corresponding getter/setter calls.

After that, we can create the repository class. It implements the reactive version of the PanacheRepository interface. The important thing is that the PanacheRepository interface returns Mutiny Uni objects. Therefore, if need to add some additional methods inside the repository we should also return results as the Uni object.

@ApplicationScoped
public class PersonRepository implements PanacheRepository<PersonEntity> {

    public Uni<List<PersonEntity>> findByName(String name){
        return find("name", name).list();
    }

    public Uni<List<PersonEntity>> findByAge(int age){
        return find("age", age).list();
    }
}

Finally, we can proceed to the most important element in our tutorial – the implementation of the gRPC service. The implementation class should be annotated with @GrpcService (1). In order to interact with the database reactively I also had to annotate it with @WithSession (2). It creates a Mutiny session for each method gRPC method inside. We can also register optional interceptors, for example, to log incoming requests and outgoing responses (3). Our service class needs to implement the PersonsService interface generated by the Quarkus gRPC extension (4).

Then we can go inside the class. In the first step, we will inject the repository bean (5). After that, we will override all the gRPC methods generated from the .proto manifest (6). All those methods use the PersonRepository bean to interact with the database. Once they obtain a result it is required to convert it to the Protobuf object (7). When we add a new person to the database, we need to do it in the transaction scope (8).

@GrpcService // (1)
@WithSession // (2)
@RegisterInterceptor(LogInterceptor.class) // (3)
public class PersonsServiceImpl implements PersonsService { // (4)

    private PersonRepository repository; // (5)

    public PersonsServiceImpl(PersonRepository repository) {
        this.repository = repository;
    }

    @Override // (6)
    public Uni<PersonProto.Persons> findByName(StringValue request) {
        return repository.findByName(request.getValue())
                .map(this::mapToPersons); // (7)
    }

    @Override
    public Uni<PersonProto.Persons> findByAge(Int32Value request) {
        return repository.findByAge(request.getValue())
                .map(this::mapToPersons);
    }

    @Override
    public Uni<PersonProto.Person> findById(Int64Value request) {
        return repository.findById(request.getValue())
                .map(this::mapToPerson);
    }

    @Override
    public Uni<PersonProto.Persons> findAll(Empty request) {
        return repository.findAll().list()
                .map(this::mapToPersons);
    }

    @Override
    @WithTransaction // (8)
    public Uni<PersonProto.Person> addPerson(PersonProto.Person request) {
        PersonEntity entity = new PersonEntity();
        entity.age = request.getAge();
        entity.name = request.getName();
        entity.gender = Gender.valueOf(request.getGender().name());
        return repository.persist(entity)
           .map(personEntity -> mapToPerson(entity));
    }

    private PersonProto.Persons mapToPersons(List<PersonEntity> list) {
        PersonProto.Persons.Builder builder = 
           PersonProto.Persons.newBuilder();
        list.forEach(p -> builder.addPerson(mapToPerson(p)));
        return builder.build();
    }

    private PersonProto.Person mapToPerson(PersonEntity entity) {
        PersonProto.Person.Builder builder = 
           PersonProto.Person.newBuilder();
        if (entity != null) {
            return builder.setAge(entity.age)
                    .setName(entity.name)
                    .setId(entity.id)
                    .setGender(PersonProto.Gender
                       .valueOf(entity.gender.name()))
                    .build();
        } else {
            return null;
        }
    }
}

At the end let’s discuss the optional step. With Quarkus gRPC we can implement a server interceptor by creating the @ApplicationScoped bean implementing ServerInterceptor. In order to apply an interceptor to all exposed services, we should annotate it with @GlobalInterceptor. In our case, the interceptor is registered to a single service with @RegisterInterceptor annotation. Then we will the SimpleForwardingServerCall class to log outgoing messages, and SimpleForwardingServerCallListener to log outgoing messages.

@ApplicationScoped
public class LogInterceptor  implements ServerInterceptor {

    Logger log;

    public LogInterceptor(Logger log) {
        this.log = log;
    }

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {

        ServerCall<ReqT, RespT> listener = new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
            @Override
            public void sendMessage(RespT message) {
                log.infof("[Sending message] %s",  message.toString().replaceAll("\n", " "));
                super.sendMessage(message);
            }
        };

        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(listener, headers)) {
            @Override
            public void onMessage(ReqT message) {
                log.infof("[Received message] %s", message.toString().replaceAll("\n", " "));
                super.onMessage(message);
            }
        };
    }
}

Quarkus JUnit gRPC Tests

Of course, there must be tests in our app. Thanks to the Quarkus dev services and built-in integration with Testcontainers we don’t have to take care of starting the database. Just remember to run the Docker daemon on your laptop. After annotating the test class with @QuarkusTest we can inject the gRPC client generated from the .proto manifest with @GrpcClient (1). Then, we can use the PersonsService interface to call our gRPCS methods. By default, Quarkus starts gRPC endpoints on 9000 port. The Quarkus gRPC client works in the reactive mode, so we will leverage the CompletableFuture class to obtain and verify results in the tests (2).

@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class PersonsServiceTests {

    static Long newId;

    @GrpcClient // (1)
    PersonsService client;

    @Test
    @Order(1)
    void shouldAddNew() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<Long> message = new CompletableFuture<>(); // (2)
        client.addPerson(PersonProto.Person.newBuilder()
                        .setName("Test")
                        .setAge(20)
                        .setGender(PersonProto.Gender.MALE)
                        .build())
                .subscribe().with(res -> message.complete(res.getId()));
        Long id = message.get(1, TimeUnit.SECONDS);
        assertNotNull(id);
        newId = id;
    }

    @Test
    @Order(2)
    void shouldFindAll() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<List<PersonProto.Person>> message = new CompletableFuture<>();
        client.findAll(Empty.newBuilder().build())
                .subscribe().with(res -> message.complete(res.getPersonList()));
        List<PersonProto.Person> list = message.get(1, TimeUnit.SECONDS);
        assertNotNull(list);
        assertFalse(list.isEmpty());
    }

    @Test
    @Order(2)
    void shouldFindById() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<PersonProto.Person> message = new CompletableFuture<>();
        client.findById(Int64Value.newBuilder().setValue(newId).build())
                .subscribe().with(message::complete);
        PersonProto.Person p = message.get(1, TimeUnit.SECONDS);
        assertNotNull(p);
        assertEquals("Test", p.getName());
        assertEquals(newId, p.getId());
    }

    @Test
    @Order(2)
    void shouldFindByAge() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<PersonProto.Persons> message = new CompletableFuture<>();
        client.findByAge(Int32Value.newBuilder().setValue(20).build())
                .subscribe().with(message::complete);
        PersonProto.Persons p = message.get(1, TimeUnit.SECONDS);
        assertNotNull(p);
        assertEquals(1, p.getPersonCount());
    }

    @Test
    @Order(2)
    void shouldFindByName() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<PersonProto.Persons> message = new CompletableFuture<>();
        client.findByName(StringValue.newBuilder().setValue("Test").build())
                .subscribe().with(message::complete);
        PersonProto.Persons p = message.get(1, TimeUnit.SECONDS);
        assertNotNull(p);
        assertEquals(1, p.getPersonCount());
    }
}

Running and Testing Quarkus Locally

Let’s run our Quarkus app locally in the dev mode:

$ mvn quarkus:dev

Quarkus will start the Postgres database and expose gRPC services on the port 9000:

We can go to the Quarkus Dev UI console. It is available under the address http://localhost:8080/q/dev-ui. Once you do it, you should see the gRPC tile as shown below:

quarkus-grpc-ui

Click the Services link inside that tile. You will be redirected to the site with a list of available gRPC services. After that, we may expand the row with the model.PersonsService name. It allows to perform a test call of the selected gRPC method. Let’s choose the AddPerson tab. Then we can insert the request in the JSON format and send it to the server by clicking the Send button.

quarkus-grpc-ui-send

If you don’t like UI interfaces you can use the grpcurl CLI instead. By default, the gRPC server is started on a port 9000 in the PLAINTEXT mode. In order to print a list of available services we need to execute the following command:

$ grpcurl --plaintext localhost:9000 list
grpc.health.v1.Health
model.PersonsService

Then, let’s print the list of methods exposed by the model.PersonsService:

$ grpcurl --plaintext localhost:9000 list model.PersonsService
model.PersonsService.AddPerson
model.PersonsService.FindAll
model.PersonsService.FindByAge
model.PersonsService.FindById
model.PersonsService.FindByName

We can also print the details about each method by using the describe keyword in the command:

$ grpcurl --plaintext localhost:9000 describe model.PersonsService.FindById
model.PersonsService.FindById is a method:
rpc FindById ( .google.protobuf.Int64Value ) returns ( .model.Person );

Finally, let’s call the endpoint described with the command visible above. We are going to find the previously added person (via UI) by the id field value.

$ grpcurl --plaintext -d '1' localhost:9000 model.PersonsService.FindById
{
  "id": "1",
  "name": "Test",
  "age": 20,
  "gender": "FEMALE"
}

Running Quarkus gRPC on OpenShift

In the last step, we will run our app on OpenShift and try to interact with the gRPC service through the OpenShift Route. Fortunately, we can leverage Quarkus extension for OpenShift. We can include the quarkus-openshift dependency in the optional Maven profile.

<profile>
  <id>openshift</id>
  <activation>
  <property>
    <name>openshift</name>
  </property>
  </activation>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-openshift</artifactId>
    </dependency>
  </dependencies>
  <properties>
    <quarkus.kubernetes.deploy>true</quarkus.kubernetes.deploy>
    <quarkus.profile>openshift</quarkus.profile>
  </properties>
</profile>

Once we run a Maven build with the option -Popenshift it will activate the profile. Thanks to that Quarkus will handle all things required for building image and running it on the target cluster.

$ mvn clean package -Popenshift -DskipTests

In order to test our gRPC service via the OpenShift Route we need to expose it over SSL/TLS. Here’s the secret that contains both the certificate and private key. It was issued by the cert-manager for the the Route hostname.

Quarkus Kubernetes Extension offers the ability to automatically generate Kubernetes resources based on the defaults and user-supplied configuration using dekorate. It currently supports generating resources for vanilla Kubernetes, OpenShift, and Knative.

We need to configure several things in the Quarkus app. We didn’t have to take care of it when running in the dev mode. First of all, we need to provide Postgres database connection settings (1). Thanks to the Quarkus OpenShift module we can generate YAML manifest using configuration properties. The database is available on OpenShift under the person-db address. The rest of the credentials can be taken from the person-db secret (2).

Then we will mount a secret with a TLS certificate and private key to the DeploymentConfig (3). It will be available inside the pod under the /mnt/app-secret path. Then, we can enable SSL for the gRPC service by setting the certificate and private key for the server (4). We should also enable the reflection service, to allow tools like grpcurl to interact with our services. Once the SSL/TLS for the service is configured, we can create the passthrough Route that exposes it outside the OpenShift cluster (5).

# (1)
%prod.quarkus.datasource.username = ${POSTGRES_USER}
%prod.quarkus.datasource.password = ${POSTGRES_PASSWORD}
%prod.quarkus.datasource.reactive.url = vertx-reactive:postgresql://person-db:5432/${POSTGRES_DB}

# (2)
quarkus.openshift.env.mapping.postgres_user.from-secret = person-db
quarkus.openshift.env.mapping.postgres_user.with-key = database-user
quarkus.openshift.env.mapping.postgres_password.from-secret = person-db
quarkus.openshift.env.mapping.postgres_password.with-key = database-password
quarkus.openshift.env.mapping.postgres_db.from-secret = person-db
quarkus.openshift.env.mapping.postgres_db.with-key = database-name

# (3)
quarkus.openshift.app-secret = secure-callme-cert

# (4)
%prod.quarkus.grpc.server.ssl.certificate = /mnt/app-secret/tls.crt
%prod.quarkus.grpc.server.ssl.key = /mnt/app-secret/tls.key
%prod.quarkus.grpc.server.enable-reflection-service = true

# (5)
quarkus.openshift.route.expose = true
quarkus.openshift.route.target-port = grpc
quarkus.openshift.route.tls.termination = passthrough

quarkus.kubernetes-client.trust-certs = true
%openshift.quarkus.container-image.group = demo-grpc
%openshift.quarkus.container-image.registry = image-registry.openshift-image-registry.svc:5000

Here are the logs of the Quarkus app after running on OpenShift. As you see the gRPC server enables reflection service and is exposed over SSL/TLS.

quarkus-grpc-logs

Let’s also display information about our app Route. We will interact with the person-grpc-service-demo-grpc.apps-crc.testing address using the grpcurl tool.

$ oc get route
NAME                  HOST/PORT                                        PATH   SERVICES              PORT   TERMINATION   WILDCARD
person-grpc-service   person-grpc-service-demo-grpc.apps-crc.testing          person-grpc-service   grpc   passthrough   None

Finally, we have to set the key and certificates used for securing the gRPC server as the parameters of grpcurl. It should work properly as shown below. You can also try other gRPC commands used previously for the app running locally in dev mode.

$ grpcurl -key grpc.key -cert grpc.crt -cacert ca.crt \
  person-grpc-service-demo-grpc.apps-crc.testing:443 list
grpc.health.v1.Health
model.PersonsService

Final Thoughts

Quarkus simplifies the development of gRPC services. For example, it allows us to easily generate Java classes from .proto files or configure SSL/TLS for the server with application.properties. No matter if we run it locally or remotely e.g. on the OpenShift cluster, we can easily achieve it by using Quarkus features like Dev Services or Kubernetes Extension. This article shows how to take advantage of Quarkus gRPC features and use gRPC services on OpenShift.

The post Introduction to gRPC with Quarkus appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2023/09/15/introduction-to-grpc-with-quarkus/feed/ 0 14508
An Advanced GraphQL with Quarkus https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/ https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/#comments Wed, 14 Apr 2021 09:46:25 +0000 https://piotrminkowski.com/?p=9672 In this article, you will learn how to create a GraphQL application using the Quarkus framework. Our application will connect to a database, and we will use the Quarkus Panache module as the ORM provider. On the other hand, Quarkus GraphQL support is built on top of the SmallRye GraphQL library. We will discuss some […]

The post An Advanced GraphQL with Quarkus appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to create a GraphQL application using the Quarkus framework. Our application will connect to a database, and we will use the Quarkus Panache module as the ORM provider. On the other hand, Quarkus GraphQL support is built on top of the SmallRye GraphQL library. We will discuss some more advanced GraphQL and JPA topics like dynamic filtering or relations fetching.

As an example, I will use the same application as in my previous article about Spring Boot GraphQL support. We will migrate it to Quarkus. Instead of the Netflix DGS library, we will use the already mentioned SmallRye GraphQL module. The next important challenge is to replace the ORM layer based on Spring Data with Quarkus Panache. If you would like to know more about GraphQL on Spring Boot read my article An Advanced GraphQL with Spring Boot and Netflix DGS.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. After that go to the sample-app-graphql directory. Then you should just follow my instructions.

We use the same schema and entity model as in my previous article about Spring Boot and GraphQL. Our application exposes GraphQL API and connects to H2 in-memory database. There are three entities EmployeeDepartment and Organization – each of them stored in the separated table. Let’s take a look at a visualization of relations between them.

quarkus-graphql-entities

1. Dependencies for Quarkus GraphQL

Let’s start with dependencies. We need to include SmallRye GraphQL, Quarkus Panache, and the io.quarkus:quarkus-jdbc-h2 artifact for running an in-memory database with our application. In order to generate getters and setters, we can include the Lombok library. However, we can also take an advantage of the Quarkus auto-generation support. After extending entity class with PanacheEntityBase Quarkus will also generate getters and setters. We may even extend PanacheEntity to use the default id.

<dependencies>
   <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-graphql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.16</version>
    </dependency>
</dependencies>

2. Domain Model for GraphQL and Hibernate

In short, Quarkus simplifies the creation of GraphQL APIs. We don’t have to manually define any schemas. The only thing we need to do is create a domain model and use some annotations. First things first – our domain model. To clarify, I’m using the same classes for ORM and API. Of course, we should create DTO objects to expose data as a GraphQL API, but I want to simplify our example implementation as much as I can. Here’s the Employee entity class.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String firstName;
   private String lastName;
   private String position;
   private int salary;
   private int age;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Also, let’s take a look at the Department entity.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Besides entities, we also have input parameters used in the mutations. However, the input objects are much simpler than outputs. Just to compare, here’s the DepartmentInput class.

@Data
@NoArgsConstructor
public class DepartmentInput {
   private String name;
   private Integer organizationId;
}

3. GraphQL Filtering with Quarkus

In this section, we will create a dynamic filter in GraphQL API. Our sample filter allows defining criteria for three different Employee fields: salary, age and position. We may set a single field, two of them or all. Each condition is used with the AND relation to other conditions. The class with a filter implementation is visible below. It consists of several fields represented by the FilterField objects.

@Data
public class EmployeeFilter {
   private FilterField salary;
   private FilterField age;
   private FilterField position;
}

Then, let’s take a look at the FilterField implementation. It has two parameters: operator and value. Basing on the values of these parameters we are generating a JPA Criteria Predicate. I’m just generating the most common conditions for comparison between two numbers or strings.

@Data
public class FilterField {
   private String operator;
   private String value;

   public Predicate generateCriteria(CriteriaBuilder builder, Path field) {
      try {
         int v = Integer.parseInt(value);
         switch (operator) {
         case "lt": return builder.lt(field, v);
         case "le": return builder.le(field, v);
         case "gt": return builder.gt(field, v);
         case "ge": return builder.ge(field, v);
         case "eq": return builder.equal(field, v);
         }
      } catch (NumberFormatException e) {
         switch (operator) {
         case "endsWith": return builder.like(field, "%" + value);
         case "startsWith": return builder.like(field, value + "%");
         case "contains": return builder.like(field, "%" + value + "%");
         case "eq": return builder.equal(field, value);
         }
      }

      return null;
   }
}

After defining model classes we may proceed to the repository implementation. We will use PanacheRepository for that. The idea behind that is quite similar to the Spring Data Repositories. However, we don’t have anything similar to the Spring Data Specification interface which can be used to execute JPA criteria queries. Since we need to build a query basing on dynamic criteria, it would be helpful. Assuming that, we need to inject EntityManager into the repository class and use it directly to obtain JPA CriteriaBuilder. Finally, we are executing a query with criteria and returning a list of employees matching input conditions.

@ApplicationScoped
public class EmployeeRepository implements PanacheRepository<Employee> {

   private EntityManager em;

   public EmployeeRepository(EntityManager em) {
      this.em = em;
   }

   public List<Employee> findByCriteria(EmployeeFilter filter) {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Employee> criteriaQuery = builder.createQuery(Employee.class);
      Root<Employee> root = criteriaQuery.from(Employee.class);
      Predicate predicate = null;
      if (filter.getSalary() != null)
         predicate = filter.getSalary().generateCriteria(builder, root.get("salary"));
      if (filter.getAge() != null)
         predicate = (predicate == null ?
            filter.getAge().generateCriteria(builder, root.get("age")) :
            builder.and(predicate, filter.getAge().generateCriteria(builder, root.get("age"))));
      if (filter.getPosition() != null)
         predicate = (predicate == null ? filter.getPosition().generateCriteria(builder, root.get("position")) :
            builder.and(predicate, filter.getPosition().generateCriteria(builder, root.get("position"))));

      if (predicate != null)
         criteriaQuery.where(predicate);

      return em.createQuery(criteriaQuery).getResultList();
   }

}

In the last step in this section, we are creating GraphQL resources. In short, all we need to do is to annotate the class with @GraphQLApi and methods with @Query or @Mutation. If you define any input parameter in the method you should annotate it with @Name. The class EmployeeFetcher is responsible just for defining queries. It uses built-in methods provided by PanacheRepository and our custom search method created inside the EmployeeRepository class.

@GraphQLApi
public class EmployeeFetcher {

   private EmployeeRepository repository;

   public EmployeeFetcher(EmployeeRepository repository){
      this.repository = repository;
   }

   @Query("employees")
   public List<Employee> findAll() {
      return repository.listAll();
   }

   @Query("employee")
   public Employee findById(@Name("id") Long id) {
      return repository.findById(id);
   }

   @Query("employeesWithFilter")
   public List<Employee> findWithFilter(@Name("filter") EmployeeFilter filter) {
      return repository.findByCriteria(filter);
   }

}

4. Fetching Relations with Quarkus GraphQL

As you probably figured out, all the JPA relations are configured in a lazy mode. To fetch them we should explicitly set such a request in our GraphQL query. For example, we may query all departments and fetch organization to each of the departments returned on the list. Let’s analyze the request visible below. It contains field organization related to the @ManyToOne relation between Department and Organization entities.

{
  departments {
    id
    name
    organization {
      id
      name
    }
  }
}

How to handle it on the server-side? Firstly, we need to detect the existence of such a relationship field in our GraphQL query. In order to analyze the input query, we can use DataFetchingEnvironment and DataFetchingFieldSelectionSet objects. Then we need to prepare different JPA queries depending on the parameters set in the GraphQL query. Once again, we will use JPA Criteria for that. The same as before, we place implementation responsible for performing a dynamic join inside the repository bean. Also, to obtain DataFetchingEnvironment we first need to inject GraphQL Context bean. With the following DepartmentRepository implementation, we are avoiding possible N+1 problem, and fetching only the required relation.

@ApplicationScoped
public class DepartmentRepository implements PanacheRepository<Department> {

   private EntityManager em;
   private Context context;

   public DepartmentRepository(EntityManager em, Context context) {
      this.em = em;
      this.context = context;
   }

   public List<Department> findAllByCriteria() {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Department> criteriaQuery = builder.createQuery(Department.class);
      Root<Department> root = criteriaQuery.from(Department.class);
      DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
      DataFetchingFieldSelectionSet selectionSet = dfe.getSelectionSet();
      if (selectionSet.contains("employees")) {
         root.fetch("employees", JoinType.LEFT);
      }
      if (selectionSet.contains("organization")) {
         root.fetch("organization", JoinType.LEFT);
      }
      criteriaQuery.select(root).distinct(true);
      return em.createQuery(criteriaQuery).getResultList();
   }

   public Department findByIdWithCriteria(Long id) {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Department> criteriaQuery = builder.createQuery(Department.class);
      Root<Department> root = criteriaQuery.from(Department.class);
      DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
      DataFetchingFieldSelectionSet selectionSet = dfe.getSelectionSet();
      if (selectionSet.contains("employees")) {
         root.fetch("employees", JoinType.LEFT);
      }
      if (selectionSet.contains("organization")) {
         root.fetch("organization", JoinType.LEFT);
      }
      criteriaQuery.where(builder.equal(root.get("id"), id));
      return em.createQuery(criteriaQuery).getSingleResult();
   }
}

Finally, we just need to create a resource controller. It uses our custom JPA queries defined in DepartmentRepository.

@GraphQLApi
public class DepartmentFetcher {

   private DepartmentRepository repository;

   DepartmentFetcher(DepartmentRepository repository) {
      this.repository = repository;
   }

   @Query("departments")
   public List<Department> findAll() {
      return repository.findAllByCriteria();
   }

   @Query("department")
   public Department findById(@Name("id") Long id) {
      return repository.findByIdWithCriteria(id);
   }

}

5. Handling GraphQL Mutations with Quarkus

In our sample application, we separate the implementation of queries from mutations. So, let’s take a look at the DepartmentMutation class. Instead of @Query we use @Mutation annotation on the method. We also use the DepartmentInput object as a mutation method parameter.

@GraphQLApi
public class DepartmentMutation {

   private DepartmentRepository departmentRepository;
   private OrganizationRepository organizationRepository;

   DepartmentMutation(DepartmentRepository departmentRepository, 
         OrganizationRepository organizationRepository) {
      this.departmentRepository = departmentRepository;
      this.organizationRepository = organizationRepository;
   }

   @Mutation("newDepartment")
   public Department newDepartment(@Name("input") DepartmentInput departmentInput) {
      Organization organization = organizationRepository
         .findById(departmentInput.getOrganizationId());
      Department department = new Department(null, departmentInput.getName(), null, organization);
      departmentRepository.persist(department);
      return department;
   }

}

6. Testing with GraphiQL

Once we finished the implementation we may build and start our Quarkus application using the following Maven command.

$ mvn package quarkus:dev

After startup, the application is available on 8080 port. We may also take a look at the list of included Quarkus modules.

quarkus-graphql-startup

Quarkus automatically generates a GraphQL schema based on the source code. In order to display it, you should invoke the URL http://localhost:8080/graphql/schema.graphql. Of course, it is an optional step. But something that will be pretty useful for us is the GraphiQL tool. It is embedded into the Quarkus application. It allows us to easily interact with GraphQL APIs and can be accessed from http://localhost:8080/graphql-ui/. First, let’s run the following query that tests a filtering feature.

{
  employeesWithFilter(filter: {
    salary: {
      operator: "gt"
      value: "19000"
    },
    age: {
      operator: "gt"
      value: "30"
    }
  }) {
    id
    firstName
    lastName
    position
  }
}

Here’s the SQL query generated by Hibernate for our GraphQL query.

select
   employee0_.id as id1_1_,
   employee0_.age as age2_1_,
   employee0_.department_id as departme7_1_,
   employee0_.firstName as firstnam3_1_,
   employee0_.lastName as lastname4_1_,
   employee0_.organization_id as organiza8_1_,
   employee0_.position as position5_1_,
   employee0_.salary as salary6_1_ 
from
   Employee employee0_ 
where
   employee0_.salary>19000 
   and employee0_.age>30

Our sample application inserts some test data to the H2 database on startup. So, we just need to execute a query using GraphiQL.

Now, let’s repeat the same exercise to test the join feature. Here’s our GraphQL input query responsible for fetching relation with the Organization entity.

{
  department(id: 5) {
    id
    name
    organization {
      id
      name
    }
  }
}

Hibernate generates the following SQL query for that.

select
   department0_.id as id1_0_0_,
   organizati1_.id as id1_2_1_,
   department0_.name as name2_0_0_,
   department0_.organization_id as organiza3_0_0_,
   organizati1_.name as name2_2_1_ 
from
   Department department0_ 
left outer join
   Organization organizati1_ 
      on department0_.organization_id=organizati1_.id 
where
   department0_.id=5

Once again, let’s view the response for our query using GraphiQL.

Final Thoughts

GraphQL support in Quarkus, like several other features, is based on the SmallRye project. In Spring Boot, we can use third-party libraries that provide GraphQL support. One of them is Netflix DGS. There is also a popular Kickstart GraphQL library described in this article. However, we can’t use any default implementation developed by the Spring Team.

With Quarkus GraphQL support we can easily migrate from Spring Boot to Quarkus. I would not say that currently, Quarkus offers many GraphQL features like for example Netflix DGS, but it is still under active development. We could also easily replace Spring Data with the Quarkus Panache project. The lack of some features similar to the Spring Data Specification, could be easily bypassed by using JPA CriteriaBuilder and EntityManager directly. Finally, I really like the Quarkus GraphQL support, because I don’t have to take care of the GraphQL schema creation, which is generated automatically basing on the source code and annotations.

The post An Advanced GraphQL with Quarkus appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/feed/ 8 9672