Part 5: Infrastructure
Create a compose.yml in the root of your swim-ffice-consumer project. This file defines the complete local stack:
| Service | Image | Host Ports | Purpose |
|---|---|---|---|
| ffice-consumer-validator | Local build | 8086, 8447 | Mock SM API + event generator |
| ffice-consumer-validator-artemis | Local build (Artemis) | 5673, 5674, 8166 | AMQP broker |
| ffice-consumer-validator-mariadb | mariadb:12-ubi | 3310 | Validator persistence |
| mongodb | mongodb-community-server:8.2 | 27019 | Consumer event store |
| mongo-express | mongo-express:1.0 | 9084 | MongoDB web UI |
| kafka | apache/kafka:4.1.2 | 9093 | Event streaming |
| akhq | tchiotludo/akhq | 9085 | Kafka web UI |
| kafka-init | apache/kafka:4.1.2 | -- | Creates Kafka topics on startup |
compose.yml
services:
ffice-consumer-validator:
build:
context: ../swim-ffice-consumer-validator
dockerfile: src/main/docker/Containerfile.jvm
container_name: ffice-consumer-validator
ports:
- "8086:8080"
- "8447:8443"
environment:
AMQP_BROKER_HOST: ffice-consumer-validator-artemis
AMQP_BROKER_PORT: 5672
AMQP_BROKER_USERNAME: admin
AMQP_BROKER_PASSWORD: admin
MARIADB_HOST: ffice-consumer-validator-mariadb
MARIADB_PORT: 3306
MARIADB_DATABASE: swim_ffice_consumer_validator
MARIADB_USERNAME: swim
MARIADB_PASSWORD: swim
EVENT_GENERATOR_ENABLED: "true"
EVENT_GENERATOR_SCHEDULE: "0 */1 * * * ?"
HEARTBEAT_PUBLISHER_ENABLED: "true"
HEARTBEAT_PUBLISHER_INTERVAL: 15s
QUARKUS_HTTP_INSECURE_REQUESTS: enabled
volumes:
- ./certs/validator.crt:/certs/server/tls.crt:ro,Z
- ./certs/validator.key:/certs/server/tls.key:ro,Z
- ./certs/ca.crt:/certs/ca/ca.crt:ro,Z
networks:
- swim-network
depends_on:
ffice-consumer-validator-artemis:
condition: service_healthy
ffice-consumer-validator-mariadb:
condition: service_healthy
ffice-consumer-validator-artemis:
build:
context: ./src/local-dev/artemis
dockerfile: Containerfile
container_name: ffice-consumer-validator-artemis
ports:
- "5673:5672"
- "5674:5671"
- "8166:8161"
environment:
ARTEMIS_USER: admin
ARTEMIS_PASSWORD: admin
volumes:
- ./certs/broker.p12:/certs/broker.p12:ro,Z
- ./certs/ca-truststore.p12:/certs/ca-truststore.p12:ro,Z
networks:
- swim-network
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8161/console/ || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
ffice-consumer-validator-mariadb:
image: docker.io/library/mariadb:12-ubi
container_name: ffice-consumer-validator-mariadb
ports:
- "3310:3306"
environment:
MARIADB_DATABASE: swim_ffice_consumer_validator
MARIADB_USER: swim
MARIADB_PASSWORD: swim
MARIADB_ROOT_PASSWORD: root
volumes:
- ffice-consumer-validator-mariadb-data:/var/lib/mysql
networks:
- swim-network
healthcheck:
test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
mongodb:
image: quay.io/mongodb/mongodb-community-server:8.2-ubi9-slim
container_name: ffice-consumer-mongodb
command: ["mongod", "--quiet", "--logpath", "/dev/null"]
environment:
MONGO_INITDB_DATABASE: swim_ffice
GLIBC_TUNABLES: glibc.cpu.hwcaps=-SHSTK
ports:
- "27019:27017"
volumes:
- ffice-consumer-mongodb-data:/data/db
networks:
- swim-network
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 3
mongo-express:
image: docker.io/library/mongo-express:1.0
container_name: ffice-consumer-mongo-express
environment:
ME_CONFIG_MONGODB_URL: mongodb://mongodb:27017/
ME_CONFIG_BASICAUTH: false
ports:
- "9084:8081"
networks:
- swim-network
depends_on:
mongodb:
condition: service_healthy
kafka:
image: docker.io/apache/kafka:4.1.2
hostname: ffice-kafka
container_name: ffice-kafka
ports:
- "9093:9092"
networks:
- swim-network
environment:
KAFKA_BROKER_ID: 1
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://ffice-kafka:29092,PLAINTEXT_HOST://localhost:9093
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_NODE_ID: 1
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@ffice-kafka:29093
KAFKA_LISTENERS: PLAINTEXT://ffice-kafka:29092,CONTROLLER://ffice-kafka:29093,PLAINTEXT_HOST://0.0.0.0:9092
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LOG_DIRS: /tmp/kraft-combined-logs
CLUSTER_ID: FFICE_CONSUMER_LOCAL
KAFKA_AUTO_CREATE_TOPICS_ENABLE: false
akhq:
image: docker.io/tchiotludo/akhq:latest
container_name: ffice-consumer-akhq
environment:
AKHQ_CONFIGURATION: |
akhq:
connections:
local:
properties:
bootstrap.servers: "ffice-kafka:29092"
ports:
- "9085:8080"
networks:
- swim-network
depends_on:
- kafka
kafka-init:
image: docker.io/apache/kafka:4.1.2
container_name: ffice-kafka-init
depends_on:
- kafka
networks:
- swim-network
restart: "no"
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo 'Waiting for Kafka...'
until /opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --list > /dev/null 2>&1; do sleep 2; done
echo 'Creating FF-ICE topics...'
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-inbox-topic --partitions 10 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-flight-plan-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-flight-update-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-operations-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-trial-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-submission-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-data-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-dlq-topic --partitions 1 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-events-topic --partitions 3 --replication-factor 1
/opt/kafka/bin/kafka-topics.sh --bootstrap-server ffice-kafka:29092 --create --if-not-exists --topic ffice-events-dlq-topic --partitions 1 --replication-factor 1
echo 'Done.'
volumes:
ffice-consumer-mongodb-data:
ffice-consumer-validator-mariadb-data:
networks:
swim-network:
name: swim-ffice-consumer-network
driver: bridge
5.1 Artemis broker files
The Artemis service requires custom files in src/local-dev/artemis/. Create this directory structure:
src/local-dev/artemis/
Containerfile
broker.xml
tls-entrypoint.sh
Containerfile
FROM apache/activemq-artemis:2.41.0
USER root
COPY tls-entrypoint.sh /opt/tls-entrypoint.sh
RUN chmod +x /opt/tls-entrypoint.sh
USER artemis
COPY broker.xml /var/lib/artemis-instance/etc/broker.xml
ENTRYPOINT ["/opt/tls-entrypoint.sh"]
broker.xml
Create a standard Artemis broker.xml with AMQP and AMQPS acceptors. The key configuration is:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<name>ffice-consumer-validator-artemis</name>
<persistence-enabled>true</persistence-enabled>
<journal-type>NIO</journal-type>
<acceptors>
<acceptor name="amqp">tcp://0.0.0.0:5672?protocols=AMQP;tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpCredits=1000;amqpLowCredits=300;amqpMinLargeMessageSize=102400;amqpDuplicateDetection=true</acceptor>
<acceptor name="amqps">tcp://0.0.0.0:5671?protocols=AMQP;sslEnabled=true;keyStorePath=/certs/broker.p12;keyStorePassword=changeit;trustStorePath=/certs/ca-truststore.p12;trustStorePassword=changeit;needClientAuth=false</acceptor>
</acceptors>
<security-settings>
<security-setting match="#">
<permission type="createNonDurableQueue" roles="amq"/>
<permission type="deleteNonDurableQueue" roles="amq"/>
<permission type="createDurableQueue" roles="amq"/>
<permission type="deleteDurableQueue" roles="amq"/>
<permission type="createAddress" roles="amq"/>
<permission type="deleteAddress" roles="amq"/>
<permission type="consume" roles="amq"/>
<permission type="browse" roles="amq"/>
<permission type="send" roles="amq"/>
<permission type="manage" roles="amq"/>
</security-setting>
</security-settings>
<address-settings>
<address-setting match="#">
<dead-letter-address>DLQ</dead-letter-address>
<expiry-address>ExpiryQueue</expiry-address>
<redelivery-delay>1000</redelivery-delay>
<max-size-bytes>-1</max-size-bytes>
<message-counter-history-day-limit>10</message-counter-history-day-limit>
<address-full-policy>PAGE</address-full-policy>
<auto-create-queues>true</auto-create-queues>
<auto-create-addresses>true</auto-create-addresses>
</address-setting>
</address-settings>
</core>
</configuration>
tls-entrypoint.sh
#!/bin/bash
set -e
if [ -f /certs/broker.p12 ]; then
echo "TLS certificates found, starting with AMQPS support"
else
echo "No TLS certificates found, starting with AMQP only"
fi
exec /opt/activemq-artemis/bin/artemis run
5.2 Generate TLS certificates
Create a certs/generate.sh script that generates a local CA and service certificates.
#!/bin/bash
set -euo pipefail
CERTS_DIR="$(cd "$(dirname "$0")" && pwd)"
CA_PASS="changeit"
echo "=== Generating local CA ==="
mkcert -install
cp "$(mkcert -CAROOT)/rootCA.pem" "$CERTS_DIR/ca.crt"
cp "$(mkcert -CAROOT)/rootCA-key.pem" "$CERTS_DIR/ca.key"
echo "=== Generating broker certificate ==="
mkcert -cert-file "$CERTS_DIR/broker.crt" -key-file "$CERTS_DIR/broker.key" \
ffice-consumer-validator-artemis localhost 127.0.0.1
echo "=== Generating validator certificate ==="
mkcert -cert-file "$CERTS_DIR/validator.crt" -key-file "$CERTS_DIR/validator.key" \
ffice-consumer-validator localhost 127.0.0.1
echo "=== Creating PKCS12 keystores ==="
openssl pkcs12 -export -in "$CERTS_DIR/broker.crt" -inkey "$CERTS_DIR/broker.key" \
-out "$CERTS_DIR/broker.p12" -name broker -password "pass:$CA_PASS"
keytool -importcert -noprompt -alias ca -file "$CERTS_DIR/ca.crt" \
-keystore "$CERTS_DIR/ca-truststore.p12" -storetype PKCS12 -storepass "$CA_PASS"
echo "=== Creating JKS keystores for consumer ==="
keytool -importcert -noprompt -alias ca -file "$CERTS_DIR/ca.crt" \
-keystore "$CERTS_DIR/truststore.jks" -storepass "$CA_PASS"
mkcert -cert-file "$CERTS_DIR/consumer.crt" -key-file "$CERTS_DIR/consumer.key" \
ffice-consumer localhost 127.0.0.1
openssl pkcs12 -export -in "$CERTS_DIR/consumer.crt" -inkey "$CERTS_DIR/consumer.key" \
-out "$CERTS_DIR/consumer.p12" -name consumer -password "pass:$CA_PASS"
keytool -importkeystore -noprompt \
-srckeystore "$CERTS_DIR/consumer.p12" -srcstoretype PKCS12 -srcstorepass "$CA_PASS" \
-destkeystore "$CERTS_DIR/keystore.jks" -deststorepass "$CA_PASS"
echo "=== Done. Generated files: ==="
ls -la "$CERTS_DIR"
Run the script:
chmod +x certs/generate.sh # Linux / macOS only
./certs/generate.sh
On Windows, run the equivalent commands manually or use Git Bash / WSL to execute the shell script.
After running, the certs/ directory should contain:
ca.crt,ca.key- CA certificate and keybroker.p12- broker keystore (PKCS12)ca-truststore.p12- CA truststore (PKCS12)validator.crt,validator.key- validator server certificatetruststore.jks,keystore.jks- Java keystores for the consumer
Part 6: Run
6.1 Start infrastructure
From the swim-ffice-consumer directory:
podman compose up --build -d
Wait for all services to be healthy. Verify with podman compose ps.
6.2 Create the development profile
Create src/main/resources/application-dev.properties. This profile is used when running with quarkus:dev and connects to the local infrastructure from compose.yml:
# =============================================================================
# SWIM FF-ICE Consumer - Development Profile
# =============================================================================
# Uses swim-ffice-consumer-validator (port 8086) as Subscription Manager
# and ffice-consumer-validator-artemis (port 5672) as AMQP broker
# DevServices are DISABLED to prevent Testcontainers from starting
# =============================================================================
# =============================================================================
# MongoDB (ffice-consumer-mongodb on port 27019)
# =============================================================================
quarkus.mongodb.connection-string=${MONGODB_URI:mongodb://localhost:27019}
# =============================================================================
# FF-ICE Subscriptions (Hard-coded for local testing)
# =============================================================================
ffice.subscriptions=[{"provider":"validator","topic":"ffice.v1","messageTypes":["FILED_FLIGHT_PLAN","FLIGHT_PLAN_UPDATE","FLIGHT_DEPARTURE","FLIGHT_ARRIVAL","FLIGHT_CANCELLATION","PLANNING_STATUS","FILING_STATUS"],"aerodromes":["LPPT","LEMD","LFPG"],"description":"FF-ICE messages for Lisbon, Madrid, Paris"}]
# =============================================================================
# SWIM Providers (Dev - ffice-consumer-validator-artemis AMQPS + mTLS)
# SM: http://localhost:8086 (swim-ffice-consumer-validator)
# AMQP: localhost:5673 -> container 5672 (plain AMQP for dev simplicity)
# =============================================================================
swim.providers=[{"providerId":"validator","subscriptionManager":{"url":"http://localhost:8086","tls":null,"resilience":{"connectTimeoutMs":5000,"readTimeoutMs":5000,"retryMaxAttempts":3,"retryDelayMs":1000}},"amqpBroker":{"host":"localhost","port":5673,"sslEnabled":false,"username":"admin","password":"admin","tls":null}}]
# =============================================================================
# Subscription Renewal (Faster intervals for dev)
# =============================================================================
swim.subscription.renewal.check-interval=2m
swim.subscription.renewal.threshold=30m
# =============================================================================
# DevServices - DISABLED (use compose.yml instead)
# =============================================================================
quarkus.devservices.enabled=false
# =============================================================================
# Kafka
# =============================================================================
kafka.bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS:localhost:9093}
# =============================================================================
# Per-Subscription Heartbeat Monitor (faster check interval for dev)
# =============================================================================
swim.heartbeat.monitor.check-interval=10s
# =============================================================================
# OpenTelemetry - DISABLED in dev (no collector running)
# =============================================================================
quarkus.otel.enabled=false
quarkus.otel.sdk.disabled=true
# =============================================================================
# TLS
# =============================================================================
swim.tls.reload-period=off
# =============================================================================
# Logging
# =============================================================================
quarkus.log.level=INFO
quarkus.log.file.enabled=true
quarkus.log.file.path=target/app.log