SmallRye Archives - Piotr's TechBlog https://piotrminkowski.com/tag/smallrye/ Java, Spring, Kotlin, microservices, Kubernetes, containers Wed, 14 Apr 2021 10:24:01 +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 SmallRye Archives - Piotr's TechBlog https://piotrminkowski.com/tag/smallrye/ 32 32 181738725 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
Guide to Quarkus with Kotlin https://piotrminkowski.com/2020/08/09/guide-to-quarkus-with-kotlin/ https://piotrminkowski.com/2020/08/09/guide-to-quarkus-with-kotlin/#comments Sun, 09 Aug 2020 08:28:56 +0000 http://piotrminkowski.com/?p=8353 Quarkus is a lightweight Java framework developed by RedHat. It is dedicated for cloud-native applications that require a small memory footprint and a fast startup time. Its programming model is built on top of proven standards like Eclipse MicroProfile. Recently it is growing in popularity. It may be considered as an alternative to Spring Boot […]

The post Guide to Quarkus with Kotlin appeared first on Piotr's TechBlog.

]]>
Quarkus is a lightweight Java framework developed by RedHat. It is dedicated for cloud-native applications that require a small memory footprint and a fast startup time. Its programming model is built on top of proven standards like Eclipse MicroProfile. Recently it is growing in popularity. It may be considered as an alternative to Spring Boot framework, especially if you are running your applications on Kubernetes or OpenShift.
In this guide, you will learn how to implement a simple Quarkus Kotlin application, that exposes REST endpoints and connects to a database. We will discuss the following topics:

  • Implementation of REST endpoints
  • Integration with H2 with Hibernate and Panache project
  • Generating and exposing OpenAPI/Swagger documentation
  • Exposing health checks
  • Exposing basic metrics
  • Logging request and response
  • Testing REST endpoints with RestAssured library

github-logo Source code

The source code with the sample Quarkus Kotlin applications is available on GitHub. First, you need to clone the following repository: https://github.com/piomin/sample-quarkus-applications.git. Then, you need to go to the employee-service directory.

1. Enable Quarkus Kotlin support

To enable Kotlin support in Quarkus we need to include quarkus-kotlin module. We also have to add kotlin-stdlib library.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-kotlin</artifactId>
</dependency>
<dependency>
   <groupId>org.jetbrains.kotlin</groupId>
   <artifactId>kotlin-stdlib</artifactId>
</dependency>

In the next step we need to include kotlin-maven-plugin. Besides standard configuration, we have to use all-open Kotlin compiler plugin. The all-open compiler plugin makes classes annotated with a specific annotation and their members open without the explicit open keyword. Since classes annotated with @Path, @ApplicationScoped, or @QuarkusTest should not be final, we need to add all those annotations to the pluginOptions section.

<build>
   <sourceDirectory>src/main/kotlin</sourceDirectory>
   <testSourceDirectory>src/test/kotlin</testSourceDirectory>
   <plugins>
      <plugin>
         <groupId>io.quarkus</groupId>
         <artifactId>quarkus-maven-plugin</artifactId>
         <version>${quarkus-plugin.version}</version>
         <executions>
            <execution>
               <goals>
                  <goal>build</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
      <plugin>
         <groupId>org.jetbrains.kotlin</groupId>
         <artifactId>kotlin-maven-plugin</artifactId>
         <version>${kotlin.version}</version>
         <executions>
            <execution>
               <id>compile</id>
               <goals>
                  <goal>compile</goal>
               </goals>
            </execution>
            <execution>
               <id>test-compile</id>
               <goals>
                  <goal>test-compile</goal>
               </goals>
            </execution>
         </executions>
         <dependencies>
            <dependency>
               <groupId>org.jetbrains.kotlin</groupId>
               <artifactId>kotlin-maven-allopen</artifactId>
               <version>${kotlin.version}</version>
            </dependency>
         </dependencies>
         <configuration>
            <javaParameters>true</javaParameters>
            <jvmTarget>11</jvmTarget>
            <compilerPlugins>
               <plugin>all-open</plugin>
            </compilerPlugins>
            <pluginOptions>
               <option>all-open:annotation=javax.ws.rs.Path</option>
               <option>all-open:annotation=javax.enterprise.context.ApplicationScoped</option>
               <option>all-open:annotation=io.quarkus.test.junit.QuarkusTest</option>
            </pluginOptions>
         </configuration>
      </plugin>
   </plugins>
</build>

2. Implement REST endpoint

In Quarkus support for REST is built on top of Resteasy and JAX-RS libraries. You can choose between two available extentions for JSON serialization/deserialization: JsonB and Jackson. Since I decided to use Jackson I need to include quarkus-resteasy-jackson dependency. It also includes quarkus-resteasy module.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>

We mostly use JAX-RS annotations for mapping controller methods and fields into HTTP endpoints. We may also use Resteasy annotations like @PathParam, that does not require to set any fields. In order to interact with database, we are injecting a repository bean.

@Path("/employees")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class EmployeeResource(val repository: EmployeeRepository) {

    @POST
    @Transactional
    fun add(employee: Employee): Response {
        repository.persist(employee)
        return Response.ok(employee).status(201).build()
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    fun delete(@PathParam id: Long) {
        repository.deleteById(id)
    }

    @GET
    fun findAll(): List<Employee> = repository.listAll()

    @GET
    @Path("/{id}")
    fun findById(@PathParam id: Long): Employee? = repository.findById(id)

    @GET
    @Path("/first-name/{firstName}/last-name/{lastName}")
    fun findByFirstNameAndLastName(@PathParam firstName: String, @PathParam lastName: String): List<Employee>
            = repository.findByFirstNameAndLastName(firstName, lastName)

    @GET
    @Path("/salary/{salary}")
    fun findBySalary(@PathParam salary: Int): List<Employee> = repository.findBySalary(salary)

    @GET
    @Path("/salary-greater-than/{salary}")
    fun findBySalaryGreaterThan(@PathParam salary: Int): List<Employee>
            = repository.findBySalaryGreaterThan(salary)

}

3. Integration with database

Quarkus provides Panache JPA extension to simplify work with Hibernate ORM. It also provides driver extensions for the most popular SQL databases like Postgresql, MySQL, or H2. To enable both these features for H2 in-memory database we need to include the following dependencies.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-hibernate-orm-panache-kotlin</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>

We should also configure connection settings inside application.properties file.


quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=password
quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb

Panache extension allows to use well-known repository pattern. To use it we should first define entity that extends PanacheEntity class.

@Entity
data class Employee(var firstName: String = "",
                    var lastName: String = "",
                    var position: String = "",
                    var salary: Int = 0,
                    var organizationId: Int? = null,
                    var departmentId: Int? = null): PanacheEntity()

In the next step, we are defining repository bean that implements PanacheRepository interface. It comes with some basic methods like persist, deleteById or listAll. We may also use those basic methods to implement more advanced queries or operations.

@ApplicationScoped
class EmployeeRepository: PanacheRepository<Employee> {
    fun findByFirstNameAndLastName(firstName: String, lastName: String): List<Employee> =
           list("firstName = ?1 and lastName = ?2", firstName, lastName)

    fun findBySalary(salary: Int): List<Employee> = list("salary", salary)

    fun findBySalaryGreaterThan(salary: Int): List<Employee> = list("salary > ?1", salary)
}

4. Enable OpenAPI documentation for Quarkus Kotlin

It is possible to generate OpenAPI v3 specification automatically. To do that we need to include SmallRye OpenAPI extension. The specification is available under path /openapi.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>

We may provide some additional informations to the generated OpenAPI specification like description or version number. To do that we need to create application class that extends javax.ws.rs.core.Application, and annotate it with @OpenAPIDefinition, as shown below.

@OpenAPIDefinition(info = Info(title = "Employee API", version = "1.0"))
class EmployeeApplication: Application()

Usually, we want to expose OpenAPI specification using Swagger UI. Such a feature may be enabled using configuration property quarkus.swagger-ui.always-include=true.

quarkus-swagger

5. Health checks

We may expose built-in health checks implementation by including SmallRye Health extension.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

It exposes three REST endpoints compliant with Kubernetes health checks pattern:

  • /health/live – The application is up and running (Kubernetes liveness probe).
  • /health/ready – The application is ready to serve requests (Kubernetes readiness probe).
  • /health – Accumulating all health check procedures in the application.

The default implementation of readiness health check verifies database connection status, while liveness just determines if the application is running.

quarkus-readiness

6. Expose metrics

We may enable metrics collection by adding SmallRye Metrics extension. By default, it collects only JVM, CPU and processes metrics.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-metrics</artifactId>
</dependency>

We may force the library to collect metrics from JAX-RS endpoints. To do that we need to annotate the selected endpoints with @Timed.

@POST
@Transactional
@Timed(name = "add", unit = MetricUnits.MILLISECONDS)
fun add(employee: Employee): Response {
   repository.persist(employee)
   return Response.ok(employee).status(201).build()
}

Now, we may call endpoint POST /employee 100 times in a row. Here’s the list of metrics generated for the single endpoint. If you would like to ensure compatibility with Micrometer metrics format you need to set the following configuration property: quarkus.smallrye-metrics.micrometer.compatibility=true.

quarkus-metrics

7. Logging request and response for Quarkus Kotlin application

There is no built-in mechanism for logging HTTP requests and responses. We may implement custom logging filter that implements interfaces ContainerRequestFilter, and ContainerResponseFilter.

@Provider
class LoggingFilter: ContainerRequestFilter, ContainerResponseFilter {

    private val logger: Logger = LoggerFactory.getLogger(LoggingFilter::class.java)

    @Context
    lateinit var info: UriInfo
    @Context
    lateinit var request: HttpServerRequest

    override fun filter(ctx: ContainerRequestContext) {
        logger.info("Request {} {}", ctx.method, info.path)
    }

    override fun filter(r: ContainerRequestContext, ctx: ContainerResponseContext) {
        logger.info("Response {} {}: {}", r.method, info.path, ctx.status)
    }
    
}

8. Testing

The module quarkus-junit5 is required for testing, as it provides the @QuarkusTest annotation that controls the testing framework. The extension rest-assured is not required, but is a convenient way to test HTTP endpoints.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-junit5</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>io.rest-assured</groupId>
   <artifactId>kotlin-extensions</artifactId>
   <scope>test</scope>
</dependency>

We are adding new Employee in the first test. Then the second test verifies if there is a single Employee stored inside in-memory database.

@QuarkusTest
class EmployeeResourceTest {

    @Test
    fun testAddEmployee() {
        val emp = Employee(firstName = "John", lastName = "Smith", position = "Developer", salary = 20000)
        given().body(emp).contentType(ContentType.JSON)
                .post("/employees")
                .then()
                .statusCode(201)
    }

    @Test
    fun testGetAll() {
        given().get("/employees")
                .then()
                .statusCode(200)
                .assertThat().body("size()", `is`(1))
    }

}

Conclusion

In this guide, I showed you how to build a Quarkus Kotlin application that connects to a database and follows some best practices like exposing health checks, metrics, or logging incoming requests and outgoing responses. The last step is to run our sample application. To do that in development mode we just need to execute command mvn compile quarkus:dev. Here’s my start screen. You can see there, for example, the list of included Quarkus modules.

quarkus-run

If you are interested in Quarkus framework the next useful article for you is Guide to Quarkus on Kubernetes.

The post Guide to Quarkus with Kotlin appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/08/09/guide-to-quarkus-with-kotlin/feed/ 5 8353