Rust + Axum + lapin service
Task Gateway is a small HTTP API service that works as a task bus between clients and downstream processing services.
The service accepts task requests through HTTP, assigns a task id, serializes the request as a broker message, and publishes it to RabbitMQ. Downstream services consume messages from their own queues and process the task asynchronously.
A successful response from Task Gateway means that the task was accepted by the bus and published to RabbitMQ. It does not mean that the downstream service has completed the task.
Task Gateway is currently connected to RabbitMQ and routes tasks to these downstream service domains:
| Service domain | Service name in task key | Exchange | Queue | Task types |
|---|---|---|---|---|
| Image generation | image-generation |
images.tasks |
images.queue |
images.generate, images.edit |
| Video generation | video-generation |
videos.tasks |
videos.queue |
videos.generate, videos.animate |
The public HTTP endpoint is:
POST /api/v1/broker/publish
Content-Type: application/jsonExample request:
{
"user_id": "12345",
"task_type": "images.generate",
"payload": {
"model": "openrouter::google/gemini-3.1-flash-image-preview",
"prompt": "post-apocalyptic warrior standing in a ruined city",
"image_name": "warrior"
}
}Example response:
{
"task_key": "12345:image-generation:550e8400-e29b-41d4-a716-446655440000"
}The task_key format is:
user_id:service_name:task_uuid
Clients should store this key if they need to track the task in downstream APIs.
RabbitMQ topology is configured on the broker side, not by Task Gateway.
Task Gateway does not create queues, exchanges, or bindings. The service only checks that the selected exchange already exists and publishes a message to it. In code this is done with passive exchange declaration before publishing.
For Docker Compose, the broker topology is loaded from:
docker-compose/rabbitmq/definitions.json
RabbitMQ is configured to load that file through:
docker-compose/rabbitmq/rabbitmq.conf
Current broker configuration includes:
- exchanges:
images.tasks,videos.tasks - queues:
images.queue,videos.queue - bindings:
images.tasks->images.queuewithimages.generateimages.tasks->images.queuewithimages.editvideos.tasks->videos.queuewithvideos.generatevideos.tasks->videos.queuewithvideos.animate
If a task type points to an exchange or routing key that is not configured in RabbitMQ, publishing will fail. With mandatory: true, unroutable messages are returned by RabbitMQ and Task Gateway converts that into an error.
Task Gateway only needs the RabbitMQ connection address and the HTTP server address.
Default local configuration is stored in:
config/development.toml
Docker Compose environment values are stored in:
docker-compose/.env.task-gateway
Important variables:
TASK_GATEWAY__RUN_MODE=development
TASK_GATEWAY__SERVER__ADDRESS=0.0.0.0:10010
TASK_GATEWAY__BROKER__ADDRESS=amqp://rabbitmq:5672The broker topology itself must still be configured in RabbitMQ definitions. Do not add queues, bindings, routing keys, or exchange declarations to Task Gateway configuration.
To add a new task type for an existing service domain, update both Task Gateway code and RabbitMQ definitions.
Example: add images.upscale to the existing image service.
- Add the task type to
TaskTypeinsrc/modules/broker/models/mod.rs:
#[serde(rename = "images.upscale")]
ImagesUpscale,- Return the public routing key in
impl ToString for TaskType:
Self::ImagesUpscale => "images.upscale".into(),- Map the task type to the correct exchange in
TaskType::exchange():
Self::ImageGenerate | Self::ImageEdit | Self::ImagesUpscale => {
ServiceExchange::ImagesExchange
}- Add a RabbitMQ binding in
docker-compose/rabbitmq/definitions.json:
{
"source": "images.tasks",
"vhost": "/",
"destination": "images.queue",
"destination_type": "queue",
"routing_key": "images.upscale",
"arguments": {}
}- Update API documentation and tests so the new task type is visible to clients.
To connect a new downstream service domain, add routing support in code and create the RabbitMQ topology on the broker side.
Example: add an audio service with audio.generate.
- Add task type variants in
src/modules/broker/models/mod.rs:
#[serde(rename = "audio.generate")]
AudioGenerate,- Add a service exchange:
#[serde(rename = "audio.tasks")]
AudioExchange,- Map the task type to the exchange:
Self::AudioGenerate => ServiceExchange::AudioExchange,- Add the exchange name:
Self::AudioExchange => "audio.tasks".to_string(),- Add the service name used in
task_key:
Self::AudioExchange => "audio-generation".to_owned(),- Configure RabbitMQ in
docker-compose/rabbitmq/definitions.json:
{
"name": "audio.queue",
"vhost": "/",
"durable": true,
"auto_delete": false,
"arguments": {
"x-queue-type": "classic"
}
}{
"name": "audio.tasks",
"vhost": "/",
"type": "direct",
"durable": true,
"auto_delete": false,
"internal": false,
"arguments": {}
}{
"source": "audio.tasks",
"vhost": "/",
"destination": "audio.queue",
"destination_type": "queue",
"routing_key": "audio.generate",
"arguments": {}
}-
Make sure the new downstream service consumes from
audio.queue. -
Update Swagger descriptions and tests.
Task Gateway publishes messages with:
- direct exchange routing
- routing key equal to
task_type - persistent delivery mode
- publisher confirms enabled
mandatory: true
The message body is a serialized PublishMessage:
{
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "12345",
"task_type": "images.generate",
"payload": {
"prompt": "Generate an image"
}
}The payload object is service-specific. Task Gateway does not validate or transform it; it forwards the object to the selected downstream service.
Run tests:
cargo testRun the service locally:
cargo run --bin run_serverRun through Docker Compose:
cd docker-compose
docker compose upSwagger UI is available at:
/docs