1. General

Compile status Maven central version of Parent pom project License of Parent pom project Use JakartaEE project Commits Supported jvms GitHub Repo Stars

The goal of the project is to provide a modular solution for triggering processes by calling specific API endpoints using cron scheduling.

Components

Architecture
Figure 1. Architecture
Table 1. Ticker backend {release_version}
Type Module Version maven

External

Internal

Coffee

-

2.9.0

2. Backend

The Ticker project can be divided into two parts. One is a sampler and its associated testsuite subproject, and the other part is the ticker-core-quartz module.

2.1. Service

The sampler demonstrates how to use the module to create a service where you only need to link the apit at the dependency level with the ticker-core-quartz module.

The first step is to include the ticker-core-quartz module in the dependencies.

<dependency>
    <groupId>hu.icellmobilsoft.ticker</groupId>
    <artifactId>ticker-core-quartz</artifactId>
</dependency>

Secondly, use the API dependency containing the interfaces that the module will call:

Example API dependency
<dependency>
    <groupId>hu.icellmobilsoft.sampler.api</groupId>
    <artifactId>api</artifactId>
</dependency>
It is important that since this is a Quarkus-based module, the implementing module must also use an application with the same version of Quarkus.

After that, when the application starts, the event defined in the SchedulerController will be triggered.

At this point, the SchedulerController will see the indexed interfaces and will start them according to the provided yml config.

2.2. Ticker sampler

Mock GET Rest endpoint
GET /test
Mock POST Rest endpoint
POST /test

The two mock endpoints serve as examples to test the module; there is no functionality behind them.

3. Configuration

3.1. Backend

The module configuration is done through application.yml, which allows specifying the necessary values based on Quarkus.

The basic configuration is provided through application.yml, which can be expanded and may vary by environment, but you only need to specify values that differ from the default settings.

Possible methods in order of priority:

  • System variables

  • Environment variables

3.2. Job Configuration

The jobs to be executed are configured in the application.yml file.

Basic configurations are set at the microprofile-config.yml level in the ticker-core-quartz dependency, so this file should not be overridden in the implementing project.

application.yml - Defining Quarkus REST clients
quarkus:
    rest-client:
        ITickerTestRestRegisteredClient: (1)
            url: http://localhost:8080 (2)
            scope: jakarta.enterprise.context.ApplicationScoped (3)
            read-timeout: 5000 (4)
            connect-timeout: 5000 (5)
It is a general solution in Quarkus that the definitions of REST clients to be used in the application can be specified in this way.

The example contains the most basic configurations:

1 Under the quarkus.rest-client config, the identifier of the REST client interface must be given, which is the interface with its package, or if the configKey is provided in the @RegisterRestClient annotation, then it should be referenced here based on that.
2 This is the place to specify the baseUri.
3 Determination of the scope of the injected RestClient interface. It’s recommended to use ApplicationScoped, as it can avoid many issues (rest client closure, memory leaks, etc.); be careful not to try to destroy the bean if multiple jobs use the interface, as it will lead to errors. Increasing the thread pool is recommended if many jobs need to run on the same REST client.
4 The response waiting timeout can be defined.
5 The connection timeout can be defined.

For increasing the thread pool mentioned in point 3, the DefaultRestClientBuilderListener class should be inherited and the onNewBuilder method overridden, where the thread pool and the max-pooled-per-route parameters can be adjusted:

    @Override
    public void onNewBuilder(RestClientBuilder builder) {
        super.onNewBuilder(builder);
        builder.property("resteasy.connectionPoolSize",75);
        builder.property("resteasy.maxPooledPerRoute",50);
application.yml - Defining the list of Jobs
ticker:
    timer:
        activeJobs:
            - TEST_REST
            - TEST_REST_2
            - ..

3.2.1. Using Microprofile Rest-based Job

The job allows HTTP calls to be initiated with the help of an MP REST client.

The following example demonstrates its use:

application.yml
ticker:
    timer:
        activeJobs:
            - TEST_REST (1)
        job:
            TEST_REST: (2)
                code: REST_QUARTZ_TEST (3)
                cron: "*/10 * * ? * *" (4)
                actionClass: hu.icellmobilsoft.ticker.quartz.service.timer.job.mprestclient.MicroprofileRestClientJob (5)
                config:
                    mpRestClientClass: hu.icellmobilsoft.ticker.sample.service.rest.test.api.ITickerTestRestRegisteredClient (6)
                    method: getTest(java.lang.String,java.lang.Integer,java.lang.Long,java.lang.Boolean,java.time.OffsetDateTime) (7)
                    parameters: (8)
                        - config
                        - 50
                        - 30
                        - true
                        - 2023-06-07T13:45:27.893013372Z
1 - under ticker.timer.activeJobs, you can define which job should be active
2 - under ticker.time.job, the jobs referenced in the first point can be defined
3 - Using the code, you can search for the given job in the logs. It’s found in Health as <code>-Job, and in logs like: <<< End quartz job type [REST_QUARTZ_TEST job].
4 - Cron settings
5 - Action class that defines the job’s process.
6 - The action configuration where the mpRestClientClass can be specified, which is the REST client interface.
7 - The action configuration where the method can be specified within the REST client interface.
8 - The action configuration where the parameters for the method call can be specified. Any list element can be defined with a static method call using { at the beginning and } at the end.

3.2.2. Using HTTP Call-based Job

The job allows for the definition of basic HTTP calls. The only task to be solved is if a custom body setup is needed, for example for a POST call, which can be solved with the method definition so far.

The following example demonstrates its use:

application.yml
ticker:
    timer:
        activeJobs:
            - TEST_APACHE_HTTP_CLIENT (1)
        job:
            TEST_APACHE_HTTP_CLIENT: (2)
                code: TEST_APACHE_HTTP_CLIENT (3)
                cron: "*/1 * * ? * *" (4)
                actionClass: hu.icellmobilsoft.ticker.quartz.service.timer.job.httpclient.HttpClientJob (5)
            config:
                baseUrl: http://localhost:8080/test/ticker (6)
                method: Get (7)
                body: "&{hu.icellmobilsoft.ticker.common.util.version.BaseRequestUtil.generate}" # static method call (8)
                headers:
                    Content-Type: "application/xml"
                    Accept: "application/json"
                queryParams:
                    testString: value
                    testInteger: 1000
                    testLong: 50000
                    testBoolean: true
                    testOffsetDateTime: 2023-06-07T13:45:27.893013372Z
1 - under ticker.timer.activeJobs, you can define which job should be active
2 - under ticker.time.job, the jobs referenced in the first point can be defined
3 - Using the code, you can search for the given job in the logs. It’s found in Health as <code>-Job, and in logs like: <<< End quartz job type [TEST_APACHE_HTTP_CLIENT job].
4 - Cron settings
5 - Action class that defines the job’s process, in this example, one can use the HTTP Call-based job.
6 - The action configuration where the baseUrl can be specified for the HTTP call
7 - The action configuration where the method can be specified for the HTTP call
8 - The action configuration where the body can be specified for the HTTP call. A static method call can also be defined in the body using &{ at the beginning and } at the end.
9 - The action configuration where the headers can be specified for the HTTP call
10 - The action configuration where the queryParams can be specified for the HTTP call

4. Installation, Deployment

4.1. General Service Settings

4.2. Ticker core quartz service configuration

The Ticker Service must be accessible in the environments of the project(s) that intend to use it. To achieve this, each instance of the service - along with its infrastructural requirements - must be deployed and configured for each (development/test/production) environment.

4.2.1. Service Configuration

Application
COFFEE_APP_NAME=${quarkus.application.name}
COFFEE_CONFIG_XML_CATALOG_PATH=xsd/hu/icellmobilsoft/cfg/dto/super.catalog.xml
COFFEE_CONFIG_RESOURCE_BUNDLES= i18n.common-messages,i18n.messages
CONSOLE_LOGGING_ENABLED=true
Kubernetes deployment
  • Recommended configuration

Parameter Value Description

TICKER_LOG_CONSOLE_ENABLE

true

disable logging to console, default: true

TICKER_LOG_FILE_ENABLE

false

disable logging to file, default: false

TICKER_LOGSTASH_K8S_NAMESPACE

ticker-core-quartz-service

set K8S_NAMESPACE, default unknown

CFG_LOGSTASH_MODULE_VERSION

set moduleVersion key, default unknown

TICKER_JAEGER_AGENT_HOST_PORT

jaeger:6831

jaeger agent host, default localhost

TICKER_JAEGER_SERVICE_NAME

ticker-core-quartz-service

Service name visible on the Jaeger interface (default ROOT.war)

4.2.2. Quarkus based configs

Since the application is Quarkus based, the default Quarkus settings can be used in it.

The description can be found here: https://quarkus.io/version/3.15/guides/all-config

From the configuration list, only those elements are active that are included in the project at the dependency level.

Important elements that are already defined by default with the project:

Quarkus config key Description Env variable Default value

quarkus.arc.remove-unused-beans

Arc setting - remove unused beans: Link

-

false

quarkus.log.category."hu.icellmobilsoft".level

hu.icellmobilsoft category log level

TICKER_LOG_HU_ICELLMOBILSOFT_LEVEL

INFO

quarkus.log.console.json

Json logging enable

TICKER_LOG_CONSOLE_JSON_ENABLED

false

quarkus.log.console.format

Console log format

-

%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [thread:%t] [%c{10}] [sid:%X{extSessionId}] - %s%E%n

quarkus.log.handler.gelf.additional-field."moduleVersion".value

Gelf log - moduleVersion additional-field value

TICKER_LOGSTASH_MODULE_VERSION

unknown

quarkus.log.handler.gelf.additional-field."moduleId".value

Gelf log - moduleId additional-field value

TICKER_LOGSTASH_MODULE_ID

unknown

quarkus.log.handler.gelf.additional-field."K8S_NAMESPACE".value

Gelf log - K8S_NAMESPACE additional-field value

TICKER_LOGSTASH_K8S_NAMESPACE

unknown

quarkus.handler.gelf.include-full-mdc

Gelf log - Whether to include all fields from the MDC.

TICKER_LOGSTASH_K8S_NAMESPACE

false

quarkus.log.level

Quarkus log level: Link

TICKER_LOG_LEVEL

INFO

quarkus.log.min-level

Quarkus min log level: Link

TICKER_LOG_MIN_LEVEL

ALL

quarkus.otel.enabled

OpenTelemetry - enabled config: Link

QUARKUS_OTEL_ENABLED

false

quarkus.otel.traces.exporter

OpenTelemetry trace exporter - : Link

QUARKUS_OTEL_TRACES_EXPORTER

jaeger

quarkus.otel.exporter.otlp.traces.endpoint

OpenTelemetry endpoint - : Link

QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT

quarkus.package.jar.add-runner-suffix

Quarkus package add runner suffix: Link

-

false

quarkus.package.jar.type

Quarkus package JAR type: Link

-

uber-jar

quarkus.quartz.clustered

Quartz - clustered : Link

-

false

quarkus.quartz.thread-count

Quartz - thread count : Link

TICKER_QUARTZ_THREAD_COUNT

25

quarkus.scheduler.start-mode

Quartz - start mode : Link

-

FORCED

quarkus.smallrye-openapi.info-title

Openapi - info title : Link

-

Ticker service

quarkus.smallrye-openapi.info-version

Quartz - info version : Link

-

${quarkus.application.version}

quarkus.smallrye-openapi.info-description

Quartz - info version : Link

-

REST endpoints for operations. <br/>
General responses in case of error: <br/>
* __400__ - Bad Request <br/>
* __401__ - Unauthorized <br/>
* __404__ - Not found <br/>
* __418__ - Database object not found <br/>
* __500__ - Internal Server Error <br/>

quarkus.swagger-ui.enable

Enable swagger ui: Link

-

false

Observability
Metrics

The hu.icellmobilsoft.ticker.quartz.service.quartz.util.QuartzJobUtil class provides metrics about Quartz Jobs. We add our own hu.icellmobilsoft.ticker.quartz.service.quartz.health.metric.MetricJobListener, which implements org.quartz.JobListener, to the org.quartz.Scheduler via the org.quartz.ListenerManager interface.

Currently, the following Quartz Job metrics are available:

  • Quartz job prev fire time

    • The time of the previous Job execution

    • key: quartz_job_prev_fire_time

  • Quartz job next fire time

    • The time of the next Job execution

    • key: quartz_job_next_fire_time

  • Quartz job run time

    • The duration of the most recent Job execution

    • key: quartz_job_run_time

The application server provides metrics at the <host:port>/q/metrics endpoint with the application_ prefix.

example
application_quartz_job_prev_fire_time{configKey="REST_QUARTZ_TEST-Job",quantile="0.5"} 1.66921282E12
application_quartz_job_next_fire_time{configKey="REST_QUARTZ_TEST-Job",quantile="0.5"} 1.66921283E12
application_quartz_job_run_time{configKey="REST_QUARTZ_TEST-Job",quantile="0.5"} 41.0
Health - startup/liveness/readiness

The service supports the use of k8s probes.

4.3. Helm Config Guidelines

Quarkus supports the dev and test profiles and allows the creation of other profiles. However, the values of configurations, such as a URL, can vary across different environments.

Therefore, you need to set a custom microprofile-config.yml file at the Helm config level to override the configurations in the application.

Quarkus provides the opportunity to set config sources via environment variables: https://quarkus.io/guides/config-reference#quarkus-config-config_quarkus.config.locations

So, for helm values, the following should be set:

values.yaml
configMountPath: /deployments/app/config
...
extraEnv:
- name: QUARKUS_CONFIG_LOCATIONS
value: {{ .Values.configMountPath }}/microprofile-config.yml

5. Additional Information

5.1. Useful Commands and Accesses

Commands used for development purposes, which are used for setting up and starting developer environments.

The application can be started in several ways:

  • Starting Quarkus dev with Maven

  • Creating a Quarkus uber-jar and running this jar file using java -jar

    • The same jar placed into a java docker image and run (using Dockerfile.uber-jar.jvm)

Docker-compose is used for creating and running Docker images.

The project contains a sampler service that demonstrates the use of the module. This example is capable of running entirely on a local development machine. So there are no external dependencies.

5.1.1. Starting ticker-core-quartz-service Server in Different Ways

IDE included Quarkus run config
Several IDEs offer native support for Quarkus, as they do for Spring Boot projects, recognizing and creating their own run configuration.
Maven quarkus:dev
mvn clean compile quarkus:dev
The project consists of more than one module, as expected by Quarkus, therefore compile is necessary.
With the help of the Quarkus Maven plugin, the project can be started in dev mode, activating several dev tools. More information: https://quarkus.io/guides/dev-mode-differences.
Running Quarkus uber-jar in Docker
mvn clean install (1)
docker-compose -f <PROJECT_PATH>/ticker-backend/etc/docker-compose/docker-compose.local.ticker-service.yml up --build --force-recreate (2)
1 is necessary for generating the jar that will be included in the Docker image.
2 The docker compose command, issued in the project’s root, initiates the docker-compose build (forcing a rebuild of the image if needed with the force recreate parameter), and starts up.
Quarkus processes and optimizes beans at build-time, unlike traditional runtime dependency injection models. Thanks to this: Only the actually used classes are included in the final application, while unnecessary ones are automatically removed.
MP Rest Client can only be configured at build-time, they cannot be dynamically registered or injected at runtime, therefore at build-time it can be packaged with a Ticker core module.

6. Release notes

6.1. ticker 1.3.0

6.1.1. Changes

  • 🚀 Establishment of the open source project

6.1.2. Migration

The changes are backward compatible and do not require migration.

6.2. ticker 1.4.0

6.2.1. Changes

  • GH documents translated to english.

coffee version upgrade 2.6.02.9.0:
Migration

The changes are backward compatible and do not require migration.

roaster version upgrade 2.1.02.5.0:
Migration

The changes are backward compatible and do not require migration.

Quarkus version upgrade 3.2.5.Final3.15.3
  • Observability: OpenTracing → OpenTelemetry

  • Quarkus configuration changes

Migration
  • Observability Jaeger configuration:

    • Enable OTLP collector and set port, see docker-compose.local.observability.yml

  • MicroProfile (microprofile-config.yml) and environment variables (docker-compose.local.ticker-service.yml):

    • quarkus.jaeger.enabledquarkus.otel.enabled (QUARKUS_OTEL_ENABLED)

    • new quarkus.otel.traces.exporter (QUARKUS_OTEL_TRACES_EXPORTER)

    • quarkus.jaeger.endpointquarkus.otel.exporter.otlp.traces.endpoint (QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)

    • quarkus.package.add-runner-suffixquarkus.package.jar.add-runner-suffix

    • quarkus.package.typequarkus.package.jar.type

    • more details: https://quarkus.io/version/3.15/guides/all-config

6.2.2. Bug Fixes

docker/bake-action version upgrade: v4.3.0v5
Migration

The changes are backward compatible and do not require migration. It does not affect application functionality, only the docker-deploy workflow running on GitHub.

6.3. ticker 1.5.0

6.3.1. Changes

  • uber-jar → fast-jar

Migration

The changes are backward compatible and do not require migration.