Hands-on with Message Queues

Interact with LavinMQ and explore AMQP concepts.

This will create a user and vhost in the broker.

AMQP Tools

Book Taxi (Task 1a)

Populate Available Taxis (Task 1b)

When you click the button below, it will populate queues named "<city>" (for the cities vilnius and kaunas) with some available taxis.

Confirmed Taxis (After task 1c)

Taxi Price To From Group Name

Send Notifications (Task 2a-c)

Workshop Information

TaxiHub is a fictional ride-hailing startup operating in Vilnius and Kaunas. You're the new backend engineer, and the product team needs a booking pipeline and a driver-comms system glued together using LavinMQ. Each task adds one small microservice.

Setup

First, head to the Register tab and pick a name — this creates your user and vhost on the broker. Use that same name in the AMQP URL below (user, password, and vhost are all the same value).

AMQP URL

amqps://<name>:<name>@devopspro.lmq.cloudamqp.com/<name>

Management UI: devopspro.lmq.cloudamqp.com — use this throughout to inspect queues, exchanges, bindings, and messages.

Client libraries: any AMQP 0.9.1 client works — bunny (Ruby), pika (Python), amqp-client.js (Node), amqp-client.cr (Crystal), amqp091-go (Go), RabbitMQ.Client (.NET), and so on.

What you'll build

Task 1 — a 3-step booking pipeline: receive a request → match a taxi → price the ride.
Task 2 — a driver-comms broadcaster: route fleet notifications differently depending on how urgent and how targeted they are (everyone / one city / jump the queue).

Task 1: Consume and publish messages (using the default exchange)

  • 1a) Build your first microservice

    Goal. Receive a booking request, compute the route distance, forward it on.

    You'll learn. Connecting to LavinMQ, declaring a queue, subscribing, publishing to the default exchange by queue name, and acknowledging messages.

    Steps.

    1. Connect to LavinMQ and subscribe to the booking_requests queue.
    2. Parse the message and read from (only vilnius and kaunas are supported) and to.
    3. Compute a route distance — random is fine.
    4. Publish the enriched message to the bookings queue.
    5. Ack the original message only after publishing succeeds, so a crash mid-handle doesn't silently lose work.

    Try it. Use the Book Taxi button in the Tools tab to send an input message.

    Input (booking_requests queue):
    {"from": "vilnius", "to": "stockholm"}
    Output (bookings queue):
    {"from": "vilnius", "to": "stockholm", "distance": "5000"}

    Done when. Every Book Taxi click produces a new message on bookings in the management UI.

    Task 1a flow
  • 1b) Build your second microservice

    Goal. For each booking, claim the next available taxi from that city.

    You'll learn. Pulling a single message with basic_get (vs. subscribing), and handling "no work available" gracefully.

    Steps.

    1. Subscribe to the bookings queue.
    2. Read the from city and pull one message from the queue named after it — each city has its own queue, named simply vilnius or kaunas.
    3. Merge the taxi info into the booking and publish to matched_taxis.

    Setup. Use the Populate Taxis button in the Tools tab to fill each city queue with available taxis.

    Heads up. What should happen if the city queue is empty? Two reasonable answers: drop the booking with a log, or requeue it and retry later. Either is fine — just be explicit about the choice.

    Input (bookings queue):
    {"from": "vilnius", "to": "stockholm", "distance": "5000"}
    Input (city queue vilnius or kaunas, populated by the Populate Taxis button):
    {"taxi": "KFT822"}
    Output (matched_taxis queue):
    {"from": "vilnius", "to": "stockholm", "distance": "5000", "taxi": "KFT822"}

    Done when. Each booking consumes exactly one taxi message and produces one matched_taxis message.

    Task 1b flow
  • 1c) Calculate the price

    Goal. Attach a price and finalise the booking.

    You'll learn. Chaining services through queues — the value of small, single-purpose microservices.

    Steps.

    1. Subscribe to matched_taxis.
    2. Calculate a price — anything goes (e.g. rand, or base + distance * rate).
    3. Publish the enriched message to confirmed_bookings.
    Input (matched_taxis queue):
    {"from": "vilnius", "to": "stockholm", "distance": "5000", "taxi": "KFT822"}
    Output (confirmed_bookings queue):
    {"from": "vilnius", "to": "stockholm", "distance": "5000", "taxi": "KFT822", "price": "1337"}

    Done when. Confirmed bookings appear in the Confirmed Taxis table in the Tools tab.

    Task 1c flow

You've built TaxiHub's full booking pipeline — request, match, price. Verify the end-to-end flow in the Confirmed Taxis section of the Tools tab.

Task 2: Share information (learn routing and exchanges)

TaxiHub now needs a way for HQ to talk to drivers. Different messages need different reach: some go to everyone, some go to one city, and some need to jump the queue.

Use the Send Notifications form in the Tools tab to generate input messages for these tasks.

  • 2a) Tell everyone — fanout exchange (status: "news")

    Goal. A "free fika at HQ" message should reach every taxi, regardless of city.

    You'll learn. Fanout exchanges (route to all bound queues, ignore routing key), durable exchanges, and bulk-binding queues at startup.

    Steps.

    1. Fetch the taxi list once at startup from workshop.lavinmq.com/taxis.
    2. Declare one queue per taxi using the pattern <city>_<plate> (e.g. vilnius_KFT822).
    3. Declare a fanout exchange named news and bind every taxi queue to it.
    4. Subscribe to notifications; when status == "news", publish {"info": <message>} to the news exchange.

    Reflection. Should the news exchange be durable? What breaks if it isn't and the broker restarts?

    Input (notifications queue):
    {"city": "vilnius", "message": "Free fika at HQ", "status": "news"}
    Output (fanout exchange news):
    {"info": "Free fika at HQ"}

    Done when. The same message appears in every taxi queue in the management UI.

    Task 2a flow
  • 2b) Tell one city — topic exchange (status: "alert")

    Goal. "More customers needed downtown" should reach only that city's drivers.

    You'll learn. Topic exchanges, routing keys, and binding queues to specific keys.

    Steps.

    1. Declare a topic exchange named alerts.
    2. Bind each taxi queue with the city as its routing key (so all vilnius_* queues use routing key vilnius, and likewise for kaunas).
    3. Subscribe to notifications; when status == "alert", publish {"info": <message>} to alerts using the city as the routing key.

    Heads up. If your 2a service is still running, both will pull from notifications and silently drop each other's messages (competing consumers). Stop 2a first, or merge both handlers into one service.

    Input (notifications queue):
    {"city": "vilnius", "message": "More customers needed downtown", "status": "alert"}
    Output (topic exchange alerts, routing key <city>):
    {"info": "More customers needed downtown"}

    Done when. Only that city's taxi queues receive the message — the other city's queues stay empty.

    Task 2b flow
  • 2c) Jump the queue — priority queues (status: "crisis")

    Goal. "Road blocked, reroute now" should land at the front of each driver's queue, ahead of normal alerts already waiting.

    You'll learn. Queue arguments (x-max-priority), the publish-time priority property, and why queue arguments are immutable after creation.

    Steps.

    1. First, just try redeclaring an existing taxi queue with x-max-priority: 255 and observe what happens — you'll get PRECONDITION_FAILED (406). Queue arguments can't be changed after a queue is created.
    2. Delete each <city>_<plate> queue and recreate it with x-max-priority: 255. Valid range is 0–255; higher numbers mean sooner delivery.
    3. Rebind the recreated queues to the alerts topic exchange by city.
    4. Subscribe to notifications; when status == "crisis", publish {"info": <message>} to alerts with routing key <city> and message property priority: 255.

    Heads up. The 406 closes the channel it was sent on. Use a throwaway channel for the delete-and-recreate dance so your main consumer channel survives.

    Input (notifications queue):
    {"city": "vilnius", "message": "Crash blocking Gedimino Ave", "status": "crisis"}
    Output (topic exchange alerts, routing key <city>, priority 255):
    {"info": "Crash blocking Gedimino Ave"}

    Done when. Send a normal alert and then a crisis to the same city — the crisis message sits ahead of the alert in the queue (visible via the management UI's Get messages action on a taxi queue).

    Task 2c flow