You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2024/01/31 17:28:45 UTC

(camel-quarkus-examples) 10/12: Migrate jpa-idempotent-repository example from deprecated derby container image to MariaDB

This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git

commit f7e66c11f842f4905473e5577314754d526cd53f
Author: James Netherton <ja...@gmail.com>
AuthorDate: Mon Jan 8 10:27:58 2024 +0000

    Migrate jpa-idempotent-repository example from deprecated derby container image to MariaDB
---
 jpa-idempotent-repository/README.adoc              |  72 ++++----------
 jpa-idempotent-repository/pom.xml                  |  15 ++-
 .../src/main/kubernetes/kubernetes.yml             |  79 +++++++--------
 .../src/main/kubernetes/openshift.yml              | 106 +++++++++++++--------
 .../src/main/resources/application.properties      |  28 ++++--
 .../V1.0.0__add_camel_message_processed.sql}       |  14 ++-
 .../idempotent/repository/DerbyTestResource.java   |  81 ----------------
 .../repository/JpaIdempotentRepositoryTest.java    |   4 +-
 ...va => JpaIdempotentRepositoryTestResource.java} |  39 ++++----
 9 files changed, 180 insertions(+), 258 deletions(-)

diff --git a/jpa-idempotent-repository/README.adoc b/jpa-idempotent-repository/README.adoc
index 0034b80..33ca1eb 100644
--- a/jpa-idempotent-repository/README.adoc
+++ b/jpa-idempotent-repository/README.adoc
@@ -64,46 +64,23 @@ The camel application should produce logs as below:
 2023-09-15 15:48:20,804 INFO  [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD
 ----
 
-When running in dev mode, the idempotent consumer is storing the list of already processed messages in-memory, into a h2 database.
-Later on, another database will be used when we'll package and run the application.
-Indeed, the duplicate messages will then be stored in files, into a derby database.
+The idempotent consumer is storing the list of already processed messages into a MariaDB database.
 
-== Starting and initializing the derby database in a container
+If you're wondering how the database schema was created, it happens automatically thanks to `quarkus-flyway`. On application startup, it
+creates the `my-db` database and the required `CAMEL_MESSAGEPROCESSED` table. You can find the Flyway migration script at `src/main/resources/db/migration/V1.0.0__add_camel_message_processed.sql`.
+You can find more information about Flyway in the https://quarkus.io/guides/flyway[Quarkus Flyway guide].
 
-Before packaging and running the application in JVM mode, we need to start and initialize a derby database in a container.
-So, in a first shell, please launch a derby database container:
+== Starting and initializing the MariaDB database in a container
 
-[source,shell]
-----
-docker run -p 1527:1527 az82/docker-derby:10.16
-----
-
-And from a second shell, please run the commands below in order to initialize the derby database:
+Before packaging and running the application in JVM mode, we need to start and initialize a MariaDB database in a container.
+So, in a first shell, please launch a MariaDB database container:
 
 [source,shell]
 ----
-DERBY_DOCKER_ID=$(docker ps -q  --filter ancestor=az82/docker-derby)
-docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql
-docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql
-----
-
-It should output some logs like below:
-
-[source,shell]
+docker run -e MARIADB_USER=mariadb -e MARIADB_PASSWORD=mariadb -e MARIADB_DATABASE=my-db -e MARIADB_ROOT_PASSWORD=secret -p 3306:3306 docker.io/mariadb:10.11
 ----
-$ DERBY_DOCKER_ID=$(docker ps -q --filter ancestor=az82/docker-derby)
 
-$ docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql
-Successfully copied 2.05kB to c88edda502f7:/init.sql
-
-$ docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql
-ij version 10.16
-ij> CONNECT 'jdbc:derby:my-db;create=true';
-ij> CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
-0 rows inserted/updated/deleted
-ij> CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
-0 rows inserted/updated/deleted
-----
+If successful, you should see the message `mariadbd: ready for connections` output to the console.
 
 === Package and run the application
 
@@ -120,26 +97,7 @@ mvn clean package -DskipTests
 java -jar target/quarkus-app/quarkus-run.jar
 ----
 
-Please, note that the shell running the derby database should react by printing some logs as below:
-
-[source,shell]
-----
-Booting Derby version The Apache Software Foundation - Apache Derby - 10.16.1.1 - (1901046): instance a816c00e-018a-996e-54bf-00003e718008 
-on database directory /dbs/my-db with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5c626da3 
-Loaded from file:/derby/lib/derby.jar
-java.vendor=Eclipse Adoptium
-java.runtime.version=17.0.4.1+1
-user.dir=/dbs
-os.name=Linux
-os.arch=amd64
-os.version=4.18.0-477.21.1.el8_8.x86_64
-derby.system.home=null
-derby.stream.error.field=java.lang.System.out
-Database Class Loader started - derby.database.classpath=''
-----
-
-Beyond that, notice how the application behaves the same way.
-The only variation compared to the dev mode is actually that the idempotent repository is now a derby database running in a container.
+As mentioned above, `quarkus-flyway` will automatically create the required database and tables for you.
 
 ==== Native mode
 
@@ -184,12 +142,14 @@ Check pods are running by executing:
 kubectl get pods
 ----
 
-We expect a list of two pods similar to below:
+We expect a list of three pods similar to below.
+Note that the `camel-quarkus-examples-jpa-idempotent-repository-flyway` pod will transition from `running` to `completed`, after it has completed initializing the MariaDB database.
 
 [source,shell]
 ----
 NAME                                                              READY   STATUS    RESTARTS      AGE
-camel-quarkus-examples-derby-database-deployment-76f6dc9bdnwwxn   1/1     Running   0             23s
+camel-quarkus-examples-mariadb-database-deployment-76f6dc9bdnwwxn   1/1     Running   0             23s
+camel-quarkus-examples-jpa-idempotent-repository-flyway-in2q5n5   0/1     Completed   0              23s
 camel-quarkus-examples-jpa-idempotent-repository-7c74b9cf5ph68r   1/1     Running   1 (18s ago)   23s
 ----
 
@@ -205,8 +165,8 @@ To clean up do:
 [source,shell]
 ----
 kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-jpa-idempotent-repository
-kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-derby-database
-kubectl delete configmap -l app.kubernetes.io/name=camel-quarkus-examples-derby-database
+kubectl delete all -l job-name=camel-quarkus-examples-jpa-idempotent-repository-flyway-init
+kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-mariadb-database
 ----
 
 [NOTE]
diff --git a/jpa-idempotent-repository/pom.xml b/jpa-idempotent-repository/pom.xml
index 64798a1..ea9ecf5 100644
--- a/jpa-idempotent-repository/pom.xml
+++ b/jpa-idempotent-repository/pom.xml
@@ -100,16 +100,21 @@
             <groupId>org.apache.camel.quarkus</groupId>
             <artifactId>camel-quarkus-timer</artifactId>
         </dependency>
-        <!-- Note we added a dependency to quarkus-h2 to have an in memory 
-            database during dev mode -->
+
+        <!-- Use MariaDB for the database -->
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-jdbc-h2</artifactId>
+            <artifactId>quarkus-jdbc-mariadb</artifactId>
         </dependency>
-        <!-- While in test and prod we use a derby database -->
+
+        <!-- Flyway is used to set up the MariaDB database -->
         <dependency>
             <groupId>io.quarkus</groupId>
-            <artifactId>quarkus-jdbc-derby</artifactId>
+            <artifactId>quarkus-flyway</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.flywaydb</groupId>
+            <artifactId>flyway-mysql</artifactId>
         </dependency>
 
         <!-- Test -->
diff --git a/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml
index ec62d9b..061576a 100644
--- a/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml
+++ b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml
@@ -18,77 +18,78 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: camel-quarkus-examples-derby-database-deployment
+  name: camel-quarkus-examples-mariadb-database-deployment
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
 spec:
   replicas: 1
   selector:
     matchLabels:
-      app.kubernetes.io/name: camel-quarkus-examples-derby-database
+      app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
       app.kubernetes.io/version: 3.7.0-SNAPSHOT
   template:
     metadata:
       labels:
-        app.kubernetes.io/name: camel-quarkus-examples-derby-database
+        app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
         app.kubernetes.io/version: 3.7.0-SNAPSHOT
     spec:
       containers:
-        - name: derby-database
-          # Use a default configured derby database for example purpose, think twice before deploying to production
-          image: az82/docker-derby:10.16
+        - name: mariadb-database
+          image: docker.io/mariadb:10.11
           ports:
-            - containerPort: 1527
+            - containerPort: 3306
+          env:
+            - name: MARIADB_USER
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-secret
+                  key: db-user
+            - name: MARIADB_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-secret
+                  key: db-password
+            - name: MARIADB_DATABASE
+              value: my-db
+            - name: MARIADB_RANDOM_ROOT_PASSWORD
+              value: generate
           volumeMounts:
-            # The /derby-init folder contains the SQL init script to create the database, the table and the sequence
-            - name: derby-database-init-script-volume
-              mountPath: /derby-init
-            # The /dbs folder is where the actual database content is stored
-            - name: derby-database-data-volume
-              mountPath: /dbs
-          lifecycle:
-            postStart:
-              # Execute the SQL init script after the derby container has started
-              exec:
-                command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"]
+            # The /var/lib/mysql folder is where the actual database content is stored
+            - name: mariadb-database-data-volume
+              mountPath: /var/lib/mysql
       volumes:
-        # Create a volume in order to store the SQL init file
-        - name: derby-database-init-script-volume
-          configMap:
-              name: derby-database-init-script-config-map
-              defaultMode: 0744
         # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files
-        - name: derby-database-data-volume
+        - name: mariadb-database-data-volume
           emptyDir: {}
 ---
 apiVersion: v1
 kind: Service
 metadata:
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
-  name: derby-database
+  name: mariadb-database
 spec:
   ports:
-    - name: derby
-      port: 1527
-      targetPort: 1527
+    - name: mariadb
+      port: 3306
+      targetPort: 3306
   selector:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
   type: ClusterIP
 ---
 apiVersion: v1
-kind: ConfigMap
+kind: Secret
 metadata:
-  name: derby-database-init-script-config-map
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
+  name: mariadb-secret
+type: Opaque
 data:
-  init.sql: |
-    CONNECT 'jdbc:derby:my-db;create=true';
-    CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
-    CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
----
\ No newline at end of file
+  # mariadb
+  db-user: bWFyaWFkYg==
+  # s3cr3t
+  db-password: czNjcjN0
diff --git a/jpa-idempotent-repository/src/main/kubernetes/openshift.yml b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml
index ec62d9b..e4719d0 100644
--- a/jpa-idempotent-repository/src/main/kubernetes/openshift.yml
+++ b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml
@@ -18,77 +18,105 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: camel-quarkus-examples-derby-database-deployment
+  name: camel-quarkus-examples-mariadb-database-deployment
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
 spec:
   replicas: 1
   selector:
     matchLabels:
-      app.kubernetes.io/name: camel-quarkus-examples-derby-database
+      app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
       app.kubernetes.io/version: 3.7.0-SNAPSHOT
   template:
     metadata:
       labels:
-        app.kubernetes.io/name: camel-quarkus-examples-derby-database
+        app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
         app.kubernetes.io/version: 3.7.0-SNAPSHOT
     spec:
       containers:
-        - name: derby-database
-          # Use a default configured derby database for example purpose, think twice before deploying to production
-          image: az82/docker-derby:10.16
+        - name: mariadb-database
+          image: docker.io/mariadb:10.11
           ports:
-            - containerPort: 1527
+            - containerPort: 3306
+          env:
+            - name: MARIADB_USER
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-secret
+                  key: db-user
+            - name: MARIADB_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-secret
+                  key: db-password
+            - name: MARIADB_DATABASE
+              value: my-db
+            - name: MARIADB_RANDOM_ROOT_PASSWORD
+              value: generate
           volumeMounts:
-            # The /derby-init folder contains the SQL init script to create the database, the table and the sequence
-            - name: derby-database-init-script-volume
-              mountPath: /derby-init
-            # The /dbs folder is where the actual database content is stored
-            - name: derby-database-data-volume
-              mountPath: /dbs
-          lifecycle:
-            postStart:
-              # Execute the SQL init script after the derby container has started
-              exec:
-                command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"]
+            # The /var/lib/mysql folder is where the actual database content is stored
+            - name: mariadb-database-data-volume
+              mountPath: /var/lib/mysql
       volumes:
-        # Create a volume in order to store the SQL init file
-        - name: derby-database-init-script-volume
-          configMap:
-              name: derby-database-init-script-config-map
-              defaultMode: 0744
         # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files
-        - name: derby-database-data-volume
+        - name: mariadb-database-data-volume
           emptyDir: {}
 ---
 apiVersion: v1
 kind: Service
 metadata:
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
-  name: derby-database
+  name: mariadb-database
 spec:
   ports:
-    - name: derby
-      port: 1527
-      targetPort: 1527
+    - name: mariadb
+      port: 3306
+      targetPort: 3306
   selector:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
   type: ClusterIP
 ---
 apiVersion: v1
-kind: ConfigMap
+kind: Secret
 metadata:
-  name: derby-database-init-script-config-map
   labels:
-    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/name: camel-quarkus-examples-mariadb-database
     app.kubernetes.io/version: 3.7.0-SNAPSHOT
+  name: mariadb-secret
+type: Opaque
 data:
-  init.sql: |
-    CONNECT 'jdbc:derby:my-db;create=true';
-    CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
-    CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
----
\ No newline at end of file
+  # mariadb
+  db-user: bWFyaWFkYg==
+  # s3cr3t
+  db-password: czNjcjN0
+---
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: camel-quarkus-examples-jpa-idempotent-repository-flyway-init
+spec:
+  completionMode: NonIndexed
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: camel-quarkus-examples-jpa-idempotent-repository-flyway-init
+        app.kubernetes.io/version: 3.7.0-SNAPSHOT
+    spec:
+      containers:
+        - env:
+            - name: DB_HOSTNAME
+              value: mariadb-database
+            - name: JAVA_APP_JAR
+              value: /deployments/quarkus-run.jar
+            - name: QUARKUS_INIT_AND_EXIT
+              value: "true"
+            - name: QUARKUS_FLYWAY_ENABLED
+              value: "true"
+          image: camel-quarkus-examples-jpa-idempotent-repository:3.7.0-SNAPSHOT
+          name: camel-quarkus-examples-jpa-idempotent-repository-flyway-init
+      restartPolicy: OnFailure
+      serviceAccountName: camel-quarkus-examples-jpa-idempotent-repository
diff --git a/jpa-idempotent-repository/src/main/resources/application.properties b/jpa-idempotent-repository/src/main/resources/application.properties
index c96b8e1..62948e6 100644
--- a/jpa-idempotent-repository/src/main/resources/application.properties
+++ b/jpa-idempotent-repository/src/main/resources/application.properties
@@ -26,7 +26,7 @@ timer.repeatCount = 0
 quarkus.http.port = 8085
 quarkus.http.test-port = 8085
 
-derby-hostname=localhost
+db-hostname=localhost
 
 # Uncomment if your application image is to be pushed to an external registry
 #quarkus.container-image.registry=my.docker-registry.net
@@ -37,7 +37,14 @@ derby-hostname=localhost
 #quarkus.kubernetes-client.trust-certs=true
 
 quarkus.kubernetes.image-pull-policy=IfNotPresent
-quarkus.kubernetes.env.vars.derby-hostname=derby-database
+quarkus.kubernetes.env.vars.db-hostname=mariadb-database
+
+# Enable the application to resolve the MariaDB credentials via a secret
+quarkus.kubernetes.env.secrets=mariadb-secret
+quarkus.kubernetes.env.mapping.db-user.from-secret=mariadb-secret
+quarkus.kubernetes.env.mapping.db-user.with-key=db-user
+quarkus.kubernetes.env.mapping.db-password.from-secret=mariadb-secret
+quarkus.kubernetes.env.mapping.db-password.with-key=db-password
 
 # Uncomment to set resource limits
 #quarkus.kubernetes.resources.requests.memory=64Mi
@@ -47,7 +54,7 @@ quarkus.kubernetes.env.vars.derby-hostname=derby-database
 
 # OpenShift
 quarkus.openshift.image-pull-policy=IfNotPresent
-quarkus.openshift.env.vars.derby-hostname=derby-database
+quarkus.openshift.env.vars.db-hostname=mariadb-database
 
 # Uncomment to set resource limits
 #quarkus.openshift.resources.requests.memory=64Mi
@@ -56,12 +63,13 @@ quarkus.openshift.env.vars.derby-hostname=derby-database
 #quarkus.openshift.resources.limits.cpu=1000m
 
 # JPA
-quarkus.datasource.db-kind=derby
-%dev.quarkus.datasource.db-kind=h2
-
-quarkus.datasource.jdbc.url=jdbc:derby://${derby-hostname}:1527/my-db
-%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:my-db;DB_CLOSE_DELAY=-1
-
+quarkus.datasource.db-kind=mariadb
+quarkus.datasource.devservices.db-name=my-db
 quarkus.datasource.jdbc.max-size=8
+%prod.quarkus.datasource.jdbc.url=jdbc:mariadb://${db-hostname}:3306/my-db
+%prod.quarkus.datasource.username=${db-user:mariadb}
+%prod.quarkus.datasource.password=${db-password:mariadb}
 
-quarkus.hibernate-orm.database.generation=drop-and-create
+# Flyway
+quarkus.flyway.migrate-at-start=true
+quarkus.flyway.schemas=my-db
diff --git a/jpa-idempotent-repository/src/test/resources/init.sql b/jpa-idempotent-repository/src/main/resources/db/migration/V1.0.0__add_camel_message_processed.sql
similarity index 68%
rename from jpa-idempotent-repository/src/test/resources/init.sql
rename to jpa-idempotent-repository/src/main/resources/db/migration/V1.0.0__add_camel_message_processed.sql
index bac7b01..6b0016c 100644
--- a/jpa-idempotent-repository/src/test/resources/init.sql
+++ b/jpa-idempotent-repository/src/main/resources/db/migration/V1.0.0__add_camel_message_processed.sql
@@ -15,6 +15,14 @@
 -- limitations under the License.
 --
 
-CONNECT 'jdbc:derby:my-db;create=true';
-CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
-CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
+CREATE TABLE CAMEL_MESSAGEPROCESSED (
+    id BIGINT NOT NULL,
+    createdAt TIMESTAMP(6),
+    messageId VARCHAR(255),
+    processorName VARCHAR(255),
+    PRIMARY KEY (id)
+);
+
+CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ START WITH 1 INCREMENT BY 50;
+
+ALTER TABLE CAMEL_MESSAGEPROCESSED ADD CONSTRAINT message_processed_key_constraint UNIQUE (processorName, messageId);
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java
deleted file mode 100644
index e45da44..0000000
--- a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.acme.jpa.idempotent.repository;
-
-import java.util.Map;
-
-import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
-import org.apache.camel.util.CollectionHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.utility.MountableFile;
-import org.testcontainers.utility.TestcontainersConfiguration;
-
-/**
- * The derby test resource starts a derby container. It uses fixed port number 1527.
- */
-public class DerbyTestResource<T extends GenericContainer> implements QuarkusTestResourceLifecycleManager {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DerbyTestResource.class);
-    private static final String DERBY_IMAGE_NAME = "az82/docker-derby:10.16";
-    private static final int DERBY_PORT = 1527;
-
-    private GenericContainer container;
-
-    @Override
-    public Map<String, String> start() {
-
-        LOGGER.info(TestcontainersConfiguration.getInstance().toString());
-
-        try {
-            container = new GenericContainer(DERBY_IMAGE_NAME)
-                    .withExposedPorts(DERBY_PORT)
-                    .withCopyFileToContainer(MountableFile.forClasspathResource("init.sql"), "/init.sql")
-                    .waitingFor(Wait.forListeningPort());
-            container.start();
-
-            container.execInContainer("java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver",
-                    "org.apache.derby.tools.ij", "/init.sql");
-
-            String url = "jdbc:derby://%s:%d/my-db".formatted(container.getHost(), container.getMappedPort(DERBY_PORT));
-            return CollectionHelper.mapOf(
-                    "quarkus.datasource.jdbc.url", url,
-                    "timer.period", "100",
-                    "timer.delay", "0",
-                    "timer.repeatCount", "4");
-        } catch (Exception e) {
-            LOGGER.error("An error occurred while starting the derby container", e);
-            throw new RuntimeException(e);
-        }
-    }
-
-    protected void startContainer() {
-        container.start();
-    }
-
-    @Override
-    public void stop() {
-        try {
-            if (container != null) {
-                container.stop();
-            }
-        } catch (Exception e) {
-            // ignored
-        }
-    }
-}
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
index e49b5da..476b5df 100644
--- a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
+++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
@@ -26,12 +26,10 @@ import org.junit.jupiter.api.Test;
 import static org.awaitility.Awaitility.await;
 
 @QuarkusTest
-@QuarkusTestResource(DerbyTestResource.class)
+@QuarkusTestResource(JpaIdempotentRepositoryTestResource.class)
 public class JpaIdempotentRepositoryTest {
-
     @Test
     public void contentSetShouldStartWithOneThreeFive() {
-
         await().atMost(30L, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).until(() -> {
             String contentSet = RestAssured
                     .when()
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTestResource.java
similarity index 52%
copy from jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
copy to jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTestResource.java
index e49b5da..f839cb3 100644
--- a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
+++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTestResource.java
@@ -16,30 +16,25 @@
  */
 package org.acme.jpa.idempotent.repository;
 
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
 
-import io.quarkus.test.common.QuarkusTestResource;
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
-import org.junit.jupiter.api.Test;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.util.CollectionHelper;
 
-import static org.awaitility.Awaitility.await;
-
-@QuarkusTest
-@QuarkusTestResource(DerbyTestResource.class)
-public class JpaIdempotentRepositoryTest {
-
-    @Test
-    public void contentSetShouldStartWithOneThreeFive() {
-
-        await().atMost(30L, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).until(() -> {
-            String contentSet = RestAssured
-                    .when()
-                    .get("/content-set")
-                    .then()
-                    .extract().asString();
+/**
+ * Force timer configuration for faster test assertions
+ */
+public class JpaIdempotentRepositoryTestResource implements QuarkusTestResourceLifecycleManager {
+    @Override
+    public Map<String, String> start() {
+        return CollectionHelper.mapOf(
+                "timer.period", "100",
+                "timer.delay", "0",
+                "timer.repeatCount", "4");
+    }
 
-            return contentSet != null && contentSet.startsWith("1,3,5");
-        });
+    @Override
+    public void stop() {
+        // Noop
     }
 }