DEV Community

Nathan Fallet
Nathan Fallet

Posted on

Part 6: Test and Demo - Ktor Native Worker Tutorial

In this final part, we'll explore how to set up, run, and test the complete application across different platforms.

Prerequisites

Before running the application, ensure you have:

  1. RabbitMQ Server:

    • Running locally or accessible via network
    • Default connection: localhost:5672 with guest:guest
    • You can run RabbitMQ using Docker:
      docker run -d --name rabbitmq \
        -p 5672:5672 \
        -p 15672:15672 \
        rabbitmq:3-management
    
  2. Firebase Service Account:

    • Download from Firebase Console → Project Settings → Service Accounts
    • Save as firebase-admin-sdk.json in the project root
    • Or specify custom path via SERVICE_ACCOUNT_PATH environment variable
  3. FCM Device Token:

    • Obtain from a mobile app integrated with Firebase Cloud Messaging
    • Needed to test actual notification sending

Running the Application

Running on JVM (Development)

The JVM target is ideal for development:

./gradlew jvmRun
Enter fullscreen mode Exit fullscreen mode

This command:

  • Compiles the Kotlin code for JVM
  • Starts the Ktor server on port 8080
  • Enables hot reload (in development mode)
  • Provides better logging output via Logback

Running on Native Platforms

For production deployment, use native executables:

macOS (ARM64):

./gradlew runDebugExecutableMacosArm64
Enter fullscreen mode Exit fullscreen mode

Linux (x64):

./gradlew runDebugExecutableLinuxX64
Enter fullscreen mode Exit fullscreen mode

Windows (x64):

./gradlew runDebugExecutableMingwX64
Enter fullscreen mode Exit fullscreen mode

The application will:

  1. Connect to RabbitMQ
  2. Declare exchange and queue
  3. Start consuming messages from the queue
  4. Start the HTTP server on port 8080

You should see output similar to:

[main] INFO  Application - Application started
[main] INFO  Application - Responding at http://0.0.0.0:8080
Enter fullscreen mode Exit fullscreen mode

Building Production Executables

To build optimized native executables:

./gradlew build
Enter fullscreen mode Exit fullscreen mode

Executables will be located at:

  • macOS: build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexe
  • Linux: build/bin/linuxX64/releaseExecutable/ktor-native-worker-tutorial.kexe
  • Windows: build/bin/mingwX64/releaseExecutable/ktor-native-worker-tutorial.exe

These are standalone executables that can be deployed without a JVM.

Environment Configuration

Configure the application using environment variables:

export RABBITMQ_HOST=localhost
export RABBITMQ_PORT=5672
export RABBITMQ_USER=guest
export RABBITMQ_PASSWORD=guest
export SERVICE_ACCOUNT_PATH=/path/to/firebase-admin-sdk.json
Enter fullscreen mode Exit fullscreen mode

Then run:

./gradlew jvmRun
Enter fullscreen mode Exit fullscreen mode

Or for native executables:

export SERVICE_ACCOUNT_PATH=/path/to/firebase-admin-sdk.json
./build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexe
Enter fullscreen mode Exit fullscreen mode

Testing the API

Using curl

Send a test notification request:

curl -X POST http://localhost:8080/api/notifications \
  -H "Content-Type: application/json" \
  -d '{
    "token": "your-fcm-device-token-here",
    "title": "Hello World",
    "body": "This is a test notification from Ktor Native!"
  }'
Enter fullscreen mode Exit fullscreen mode

Expected response:

HTTP/1.1 200 OK
Enter fullscreen mode Exit fullscreen mode

Using HTTPie

If you prefer HTTPie:

http POST localhost:8080/api/notifications \
  token=your-fcm-device-token-here \
  title="Hello World" \
  body="This is a test notification from Ktor Native!"
Enter fullscreen mode Exit fullscreen mode

Using Postman

  1. Create a new POST request
  2. URL: http://localhost:8080/api/notifications
  3. Headers: Content-Type: application/json
  4. Body (raw JSON):
   {
     "token": "your-fcm-device-token-here",
     "title": "Hello World",
     "body": "This is a test notification from Ktor Native!"
   }
Enter fullscreen mode Exit fullscreen mode
  1. Send the request

Understanding the Flow

When you send a request, here's what happens:

  1. HTTP Request Received:

    • Ktor receives POST to /api/notifications
    • Content negotiation deserializes JSON to NotificationPayload
  2. Message Published:

    • Route handler creates SendNotificationEvent
    • Event serialized to JSON
    • Published to RabbitMQ exchange with routing key
    • HTTP 200 OK returned immediately
  3. Message Queued:

    • RabbitMQ routes message to the queue
    • Message persisted to disk (durable queue)
  4. Message Consumed:

    • Application's consumer receives the message
    • SendNotificationHandler invoked
  5. Notification Sent:

    • Handler deserializes SendNotificationEvent
    • Calls NotificationService.sendNotification()
    • FCM API sends push notification to device

Monitoring RabbitMQ

If you're running RabbitMQ with the management plugin:

  1. Open http://localhost:15672 in your browser
  2. Login with guest:guest
  3. Navigate to "Queues" tab
  4. You should see notifications_queue
  5. View message rates, consumers, and queue depth

Verifying the Setup

Check RabbitMQ Connection

After starting the application, verify RabbitMQ connection:

  1. In RabbitMQ Management UI, go to "Connections"
  2. You should see an active connection from your application

Check Exchange and Queue

  1. Go to "Exchanges" tab
  2. Verify notifications_exchange exists (type: topic)
  3. Go to "Queues" tab
  4. Verify notifications_queue exists (durable: true)
  5. Check that it's bound to the exchange with routing key notifications.send

Check Consumer

  1. In the queue details for notifications_queue
  2. Scroll to "Consumers" section
  3. You should see 1 active consumer

Testing Error Scenarios

Invalid JSON

curl -X POST http://localhost:8080/api/notifications \
  -H "Content-Type: application/json" \
  -d '{"invalid": "data"}'
Enter fullscreen mode Exit fullscreen mode

Expected: 400 Bad Request (missing required fields)

Missing Content-Type

curl -X POST http://localhost:8080/api/notifications \
  -d '{"token": "xxx", "title": "Hi", "body": "Test"}'
Enter fullscreen mode Exit fullscreen mode

Expected: 415 Unsupported Media Type

Invalid FCM Token

curl -X POST http://localhost:8080/api/notifications \
  -H "Content-Type: application/json" \
  -d '{
    "token": "invalid-token",
    "title": "Test",
    "body": "This will fail"
  }'
Enter fullscreen mode Exit fullscreen mode

Expected: 200 OK (request accepted), but notification sending will fail in the worker. Check application logs for FCM
error.

Performance Testing

Simple Load Test with curl

for i in {1..100}; do
  curl -X POST http://localhost:8080/api/notifications \
    -H "Content-Type: application/json" \
    -d "{\"token\": \"test-$i\", \"title\": \"Test $i\", \"body\": \"Message $i\"}" &
done
wait
Enter fullscreen mode Exit fullscreen mode

This sends 100 concurrent requests. Monitor:

  • RabbitMQ queue depth
  • Application memory usage
  • Response times

Using Apache Bench

ab -n 1000 -c 10 -p payload.json -T application/json \
  http://localhost:8080/api/notifications
Enter fullscreen mode Exit fullscreen mode

Create payload.json:

{
  "token": "test-token",
  "title": "Load Test",
  "body": "Testing"
}
Enter fullscreen mode Exit fullscreen mode

Comparing JVM vs Native Performance

Startup Time

JVM:

time ./gradlew jvmRun
Enter fullscreen mode Exit fullscreen mode

Native:

time ./build/bin/macosArm64/releaseExecutable/ktor-native-worker-tutorial.kexe
Enter fullscreen mode Exit fullscreen mode

Native executables typically start much faster than JVM.

Memory Usage

Monitor memory usage while running:

On macOS/Linux:

# JVM
ps aux | grep java

# Native
ps aux | grep ktor-native-worker-tutorial
Enter fullscreen mode Exit fullscreen mode

Native executables typically use less memory than JVM.

Request Throughput

Use Apache Bench or a similar tool to compare request handling performance between JVM and native builds.

Deployment Considerations

Docker Deployment

Create a Dockerfile for native builds:

FROM alpine:latest

# Install dependencies (if any needed by your native binary)
RUN apk add --no-cache libstdc++

# Copy the native executable
COPY build/bin/linuxX64/releaseExecutable/ktor-native-worker-tutorial.kexe /app/server

# Copy Firebase service account
COPY firebase-admin-sdk.json /app/

WORKDIR /app

# Set environment variables
ENV RABBITMQ_HOST=rabbitmq
ENV SERVICE_ACCOUNT_PATH=/app/firebase-admin-sdk.json

# Run the executable
CMD ["/app/server"]
Enter fullscreen mode Exit fullscreen mode

Build and run:

docker build -t ktor-worker .
docker run -p 8080:8080 --link rabbitmq ktor-worker
Enter fullscreen mode Exit fullscreen mode

Kubernetes Deployment

The native executable is perfect for Kubernetes:

  • Fast startup time
  • Low memory footprint
  • No JVM overhead
  • Small container image size

Scaling Workers

You can run multiple instances of the application:

  • They'll all consume from the same RabbitMQ queue
  • Messages are distributed among consumers
  • Enables horizontal scaling
  • Provides high availability

Troubleshooting

Application Won't Start

Check RabbitMQ Connection:

# Verify RabbitMQ is running
docker ps | grep rabbitmq

# Check connectivity
telnet localhost 5672
Enter fullscreen mode Exit fullscreen mode

Check Firebase Service Account:

# Verify file exists
ls -la firebase-admin-sdk.json

# Verify it's valid JSON
cat firebase-admin-sdk.json | python -m json.tool
Enter fullscreen mode Exit fullscreen mode

Messages Not Being Processed

Check Consumer Status:

  • Open RabbitMQ Management UI
  • Verify consumer is connected to the queue
  • Check application logs for errors

Check Queue Bindings:

  • Verify routing key matches: notifications.send
  • Ensure exchange type is topic

Notifications Not Received

Verify FCM Token:

  • Ensure the token is valid and current
  • FCM tokens can expire or change

Check Application Logs:

  • Look for FCM API errors
  • Verify service account has correct permissions

Summary

This tutorial covered:

  • Running the application on JVM and Native platforms
  • Building production-ready native executables
  • Configuring via environment variables
  • Testing the API with various tools
  • Monitoring RabbitMQ
  • Performance testing and comparison
  • Deployment strategies
  • Troubleshooting common issues

Complete Architecture Overview

Putting it all together:

  1. Gradle Setup: Multiplatform build with JVM and Native targets
  2. Notification Service: FCM integration using Flareon library
  3. AMQP Setup: RabbitMQ integration with Kourier client
  4. Routes: Type-safe HTTP endpoints with Ktor
  5. Koin DI: Wiring all components together
  6. Testing: Running and verifying the complete system

The result is a production-ready, native-compiled backend that:

  • Handles HTTP requests asynchronously
  • Processes notifications via message queue
  • Scales horizontally
  • Deploys with minimal resources
  • Follows clean code principles

Congratulations on completing the tutorial!

Top comments (0)